Port client to TypeScript

Co-authored-by: Jack W. <Jack5079@users.noreply.github.com>
This commit is contained in:
May 2024-03-27 21:47:33 -07:00
parent 77cfa27615
commit f441e06a21
30 changed files with 619 additions and 329 deletions

264
package-lock.json generated
View file

@ -30,6 +30,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.6", "@sveltejs/vite-plugin-svelte": "^2.4.6",
"@tsconfig/svelte": "^4.0.1",
"@types/bytes": "^3.1.1", "@types/bytes": "^3.1.1",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/formidable": "^3.4.5", "@types/formidable": "^3.4.5",
@ -37,10 +38,12 @@
"discord-api-types": "^0.37.61", "discord-api-types": "^0.37.61",
"sass": "^1.57.1", "sass": "^1.57.1",
"svelte": "^3.55.1", "svelte": "^3.55.1",
"svelte-preprocess": "^5.1.3",
"tslib": "^2.6.2",
"vite": "^4.5.0" "vite": "^4.5.0"
}, },
"engines": { "engines": {
"node": ">=v16.11" "node": ">=v21"
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
@ -158,6 +161,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/@tsconfig/svelte": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
"integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
"dev": true
},
"node_modules/@types/body-parser": { "node_modules/@types/body-parser": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -246,6 +255,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/pug": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
"dev": true
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
@ -319,6 +334,12 @@
"form-data": "^4.0.0" "form-data": "^4.0.0"
} }
}, },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@ -351,6 +372,16 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@ -363,6 +394,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -445,6 +485,12 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/concat-stream": { "node_modules/concat-stream": {
"version": "1.6.2", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
@ -566,6 +612,15 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/detect-indent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/dezalgo": { "node_modules/dezalgo": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
@ -602,6 +657,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
"dev": true
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.18.20", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
@ -816,10 +877,19 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.1.3", "version": "1.1.3",
@ -834,6 +904,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -846,6 +936,12 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/has": { "node_modules/has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -916,6 +1012,16 @@
"integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==",
"dev": true "dev": true
}, },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -1048,6 +1154,27 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@ -1207,6 +1334,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@ -1342,6 +1478,18 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.29.4", "version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
@ -1382,6 +1530,18 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sander": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
"integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
"dev": true,
"dependencies": {
"es6-promise": "^3.1.2",
"graceful-fs": "^4.1.3",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2"
}
},
"node_modules/sass": { "node_modules/sass": {
"version": "1.57.1", "version": "1.57.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz",
@ -1459,6 +1619,21 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
"integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.14",
"buffer-crc32": "^0.2.5",
"minimist": "^1.2.0",
"sander": "^0.5.0"
},
"bin": {
"sorcery": "bin/sorcery"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@ -1497,6 +1672,18 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}, },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"dependencies": {
"min-indent": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/svelte": { "node_modules/svelte": {
"version": "3.55.1", "version": "3.55.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz",
@ -1518,6 +1705,69 @@
"svelte": "^3.19.0 || ^4.0.0" "svelte": "^3.19.0 || ^4.0.0"
} }
}, },
"node_modules/svelte-preprocess": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",
"integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/pug": "^2.0.6",
"detect-indent": "^6.1.0",
"magic-string": "^0.30.5",
"sorcery": "^0.11.0",
"strip-indent": "^3.0.0"
},
"engines": {
"node": ">= 16.0.0",
"pnpm": "^8.0.0"
},
"peerDependencies": {
"@babel/core": "^7.10.2",
"coffeescript": "^2.5.1",
"less": "^3.11.3 || ^4.0.0",
"postcss": "^7 || ^8",
"postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
"pug": "^3.0.0",
"sass": "^1.26.8",
"stylus": "^0.55.0",
"sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
"svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
"typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"coffeescript": {
"optional": true
},
"less": {
"optional": true
},
"postcss": {
"optional": true
},
"postcss-load-config": {
"optional": true
},
"pug": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -1538,6 +1788,12 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true
},
"node_modules/type-is": { "node_modules/type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",

View file

@ -39,6 +39,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.6", "@sveltejs/vite-plugin-svelte": "^2.4.6",
"@tsconfig/svelte": "^4.0.1",
"@types/bytes": "^3.1.1", "@types/bytes": "^3.1.1",
"@types/cookie-parser": "^1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/formidable": "^3.4.5", "@types/formidable": "^3.4.5",
@ -46,6 +47,8 @@
"discord-api-types": "^0.37.61", "discord-api-types": "^0.37.61",
"sass": "^1.57.1", "sass": "^1.57.1",
"svelte": "^3.55.1", "svelte": "^3.55.1",
"svelte-preprocess": "^5.1.3",
"tslib": "^2.6.2",
"vite": "^4.5.0" "vite": "^4.5.0"
} }
} }

View file

@ -22,7 +22,7 @@
content="width=device-width, initial-scale=1.0, user-scalable=0" content="width=device-width, initial-scale=1.0, user-scalable=0"
/> />
<script type="module" src="./svelte/index.js"></script> <script type="module" src="./svelte/index.ts"></script>
<title>monofile</title> <title>monofile</title>

View file

@ -24,7 +24,7 @@ export default async function ServeError(
// serve error // serve error
return ctx.req.header("accept").includes("text/html") ? ctx.html( return ctx.req.header("accept")?.includes("text/html") ? ctx.html(
errorPage errorPage
.replaceAll("$code", code.toString()) .replaceAll("$code", code.toString())
.replaceAll("$text", reason), .replaceAll("$text", reason),

View file

@ -1,4 +1,4 @@
.pulldown_display[name=accounts] { .pulldown_display[data-name=accounts] {
.notLoggedIn { .notLoggedIn {
.container_div { .container_div {
position:absolute; position:absolute;
@ -184,4 +184,42 @@
} }
} }
} }
}
@keyframes bounce {
0% {
top: 0.25em;
}/*
25% {
top: 0.25em;
}
75% {
top: -0.25em;
}*/
100% {
top: -0.25em;
}
}
.loader {
i {
font-style: normal;
position: relative;
animation-name: bounce;
animation-duration: 500ms;
animation-iteration-count: infinite;
animation-direction: alternate;
top:0.25em;
&:nth-of-type(1) {
animation-delay: 0ms;
}
&:nth-of-type(2) {
animation-delay: 125ms;
}
&:nth-of-type(3) {
animation-delay: 250ms;
}
}
} }

View file

@ -1,4 +1,4 @@
.pulldown_display[name=files] { .pulldown_display[data-name=files] {
.notLoggedIn { .notLoggedIn {
position:absolute; position:absolute;
top:50%; top:50%;

View file

@ -1,4 +1,4 @@
.pulldown_display[name=help] { .pulldown_display[data-name=help] {
overflow-y:auto; overflow-y:auto;

View file

@ -13,7 +13,7 @@
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;
@ -29,7 +29,7 @@
@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;
@ -45,7 +45,7 @@
flex-direction:row; flex-direction:row;
column-gap:10px; column-gap:10px;
button, input[type=text] { button, input[type=text], input[type=submit] {
background-color:#333333; background-color:#333333;
color:#DDDDDD; color:#DDDDDD;
border:none; border:none;
@ -63,7 +63,7 @@
} }
} }
button { button, input[type=submit] {
cursor:pointer; cursor:pointer;
&:hover { &:hover {

View file

@ -50,7 +50,7 @@
overflow:auto; overflow:auto;
} }
button { button, input[type=submit] {
cursor:pointer; cursor:pointer;
background-color:#393939; background-color:#393939;
color:#DDDDDD; color:#DDDDDD;

View file

@ -1,19 +1,13 @@
<script> <script lang="ts">
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.js";
/** let topbar: Topbar;
* @type Topbar
*/
let topbar;
/** let pulldown: PulldownManager;
* @type PulldownManager
*/
let pulldown;
onMount(() => { onMount(() => {
pulldownManager.set(pulldown) pulldownManager.set(pulldown)

View file

@ -1,4 +1,4 @@
<script context="module"> <script context="module" lang="ts">
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
@ -13,10 +13,10 @@
.set("help",Help) .set("help",Help)
.set("files",Files) .set("files",Files)
export const pulldownOpen = writable(false); export const pulldownOpen = writable<string|false>(false);
</script> </script>
<script> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fade, scale } from "svelte/transition"; import { fade, scale } from "svelte/transition";
@ -24,7 +24,7 @@
return $pulldownOpen return $pulldownOpen
} }
export function openPulldown(name) { export function openPulldown(name: string) {
pulldownOpen.set(name) pulldownOpen.set(name)
} }

View file

@ -1,14 +1,11 @@
<script> <script lang="ts">
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 { account } from "./stores.mjs"; import { account } from "./stores.js";
import { _void } from "./transition/_void"; import { _void } from "./transition/_void.js";
/** export let pulldown: PulldownManager;
* @type PulldownManager
*/
export let pulldown;
</script> </script>
<div id="topbar"> <div id="topbar">
@ -23,7 +20,7 @@
<!-- 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("files")}>files</button>
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>{$account.username ? `@${$account.username}` : "account"}</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> <button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
<div /> <!-- not sure what's offcenter but something is <div /> <!-- not sure what's offcenter but something is

View file

@ -1,9 +1,9 @@
<script> <script lang="ts">
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 { serverStats, refresh_stats, account } from "./stores.js"
import bytes from "bytes" import bytes from "bytes"
import AttachmentZone from "./uploader/AttachmentZone.svelte" import AttachmentZone from "./uploader/AttachmentZone.svelte"
@ -14,52 +14,44 @@
// uploads // uploads
interface Upload {
file: string | File
params: {
uploadId?: string
}
uploadStatus: {
fileId?: string,
error?: string,
}
maximized?: boolean,
viewingUrl?: boolean
}
let attachmentZone let attachmentZone
let uploads = {} let uploads: Record<string, Upload> = {}
let uploadInProgress = false let uploadInProgress = false
let notificationPermission = let notificationPermission =
globalThis?.Notification?.permission ?? "denied" globalThis?.Notification?.permission ?? "denied"
let handle_file_upload = (ev) => { let handle_file_upload = (file: Event & { detail: File|string }) => {
if (ev.detail.type == "clone") {
uploads[Math.random().toString().slice(2)] = {
type: "clone",
name: ev.detail.url,
url: ev.detail.url,
params: { uploads[Math.random().toString().slice(2)] = {
uploadId: "", file: file.detail,
},
uploadStatus: { params: {
fileId: null, uploadId: "",
error: null, },
},
}
uploads = uploads uploadStatus: {}
} else if (ev.detail.type == "upload") {
ev.detail.files.forEach((v, x) => {
uploads[Math.random().toString().slice(2)] = {
type: "upload",
name: v.name,
file: v,
params: {
uploadId: "",
},
uploadStatus: {
fileId: null,
error: null,
},
}
})
uploads = uploads
} }
uploads = uploads
} }
let handle_fetch_promise = (x, prom) => { let handle_fetch_promise = (x: string, prom: Promise<Response>) => {
return prom return prom
.then(async (res) => { .then(async (res) => {
let txt = await res.text() let txt = await res.text()
@ -81,8 +73,8 @@
], ],
}).addEventListener( }).addEventListener(
"notificationclick", "notificationclick",
({ action }) => { (event) => {
if (action === "open") { if ("action" in event && event.action === "open") {
open( open(
"/download/" + "/download/" +
uploads[x].uploadStatus.fileId uploads[x].uploadStatus.fileId
@ -115,7 +107,7 @@
let hdl = () => { let hdl = () => {
let fd = new FormData() let fd = new FormData()
if (v.params.uploadId) fd.append("uploadId", v.params.uploadId) if (v.params.uploadId) fd.append("uploadId", v.params.uploadId)
fd.append("file", v.type == "clone" ? v.url : v.file) fd.append("file", v.file)
return handle_fetch_promise(x,fetch("/api/v1/file",{ return handle_fetch_promise(x,fetch("/api/v1/file",{
method: "PUT", method: "PUT",
@ -130,10 +122,10 @@
// animation // animation
function fileTransition(node) { function fileTransition(node: HTMLElement) {
return { return {
duration: 300, duration: 300,
css: (t) => { css: (t: number) => {
let eased = circOut(t) let eased = circOut(t)
return ` return `
@ -175,7 +167,7 @@
</h1> </h1>
<p style:color="#999999"> <p style:color="#999999">
<span class="number" <span class="number"
>{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span >{$serverStats?.version ? `v${$serverStats?.version}` : "•••"}</span
>&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing >&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing
</p> </p>
@ -195,12 +187,9 @@
: ""} : ""}
> >
<h2> <h2>
{upload[1].name} {typeof upload[1].file == "string" ? upload[1].file : upload[1].file.name}
<span style:color="#999999" style:font-weight="400" <span style:color="#999999" style:font-weight="400"
>{upload[1].type}{@html upload[1].type == "upload" >{@html typeof upload[1].file == "string" ? "clone" : `upload&nbsp;(${bytes(upload[1].file.size)})`}</span>
? `&nbsp;(${bytes(upload[1].file.size)})`
: ""}</span
>
</h2> </h2>
{#if upload[1].maximized && !uploadInProgress} {#if upload[1].maximized && !uploadInProgress}
@ -319,7 +308,7 @@
{#if uploadInProgress == false} {#if uploadInProgress == false}
<!-- if required for upload, check if logged in --> <!-- if required for upload, check if logged in -->
{#if ($serverStats.accounts || {}).requiredForUpload ? !!$account.username : true} {#if $serverStats?.accounts?.requiredForUpload ? !!$account?.username : true}
<AttachmentZone <AttachmentZone
bind:this={attachmentZone} bind:this={attachmentZone}
on:addFiles={handle_file_upload} on:addFiles={handle_file_upload}
@ -352,12 +341,12 @@
<p style:color="#999999" style:text-align="center"> <p style:color="#999999" style:text-align="center">
Hosting <span class="number" style:font-weight="600" Hosting <span class="number" style:font-weight="600"
>{$serverStats.files || "•••"}</span >{$serverStats?.files ?? "•••"}</span
> >
files — Maximum filesize is files — Maximum filesize is
<span class="number" style:font-weight="600"> <span class="number" style:font-weight="600">
{ {
$serverStats.maxDiscordFiles $serverStats?.maxDiscordFiles
? bytes($serverStats.maxDiscordFileSize * $serverStats.maxDiscordFiles) ? bytes($serverStats.maxDiscordFileSize * $serverStats.maxDiscordFiles)
: "•••" : "•••"
}</span> }</span>

View file

@ -1,28 +1,30 @@
<script> <script lang="ts">
import { fade, slide } from "svelte/transition"; import { fade, slide } from "svelte/transition";
interface BaseModalOption {
name:string,
icon:string,
id: string | number | symbol | boolean
}
let activeModal; type ModalOption = BaseModalOption & {inputSettings: {password?: boolean}, id: any} | BaseModalOption & { description: string }
let modalResults;
/** type ModalOptions = ModalOption[]
* type OptionPickerReturns = {selected: any} & Record<any,any> | null
* @param mdl {name:string,icon:string,description:string,id:string}[] let activeModal: {resolve: (val: OptionPickerReturns) => void, title: string, modal: ModalOptions } | undefined;
* @returns Promise let modalResults: Record<string | number | symbol, string> = {};
*/
export function picker(title,mdl) { export function picker(title: string,mdl: ModalOptions): Promise<OptionPickerReturns> {
if (activeModal) forceCancel() if (activeModal) forceCancel()
return new Promise((resolve,reject) => { return new Promise<OptionPickerReturns>((resolve,reject) => {
activeModal = { activeModal = {
resolve, resolve,
title, title,
modal:mdl modal:mdl
} }
modalResults = { modalResults = {}
}
}) })
} }
@ -30,7 +32,7 @@
if (activeModal && activeModal.resolve) { if (activeModal && activeModal.resolve) {
activeModal.resolve(null) activeModal.resolve(null)
} }
activeModal = null activeModal = undefined
} }
</script> </script>
@ -46,9 +48,9 @@
</div> </div>
{#each activeModal.modal as option (option.id)} {#each activeModal.modal as option (option.id)}
{#if option.inputSettings} {#if "inputSettings" in option}
<div class="inp"> <div class="inp">
<img src={option.icon} alt={option.id}> <img src={option.icon} alt={option.id.toString()}>
<!-- i have to do this stupidness because of svelte but --> <!-- i have to do this stupidness because of svelte but -->
<!-- its reason for blocking this is pretty good sooooo --> <!-- its reason for blocking this is pretty good sooooo -->
@ -60,8 +62,8 @@
{/if} {/if}
</div> </div>
{:else} {:else}
<button on:click={() => {activeModal.resolve({...modalResults,selected:option.id});activeModal=null;modalResults=null;}}> <button on:click={() => {activeModal?.resolve({...modalResults,selected:option.id});activeModal=undefined;modalResults={};}}>
<img src={option.icon} alt={option.id}> <img src={option.icon} alt={option.id.toString()}>
<p>{option.name}<span><br />{option.description}</span></p> <p>{option.name}<span><br />{option.description}</span></p>
</button> </button>
{/if} {/if}

View file

@ -1,7 +1,8 @@
import { fetchAccountData, account, refreshNeeded } from "../stores.mjs" import { fetchAccountData, account, refreshNeeded } from "../stores"
import { get } from "svelte/store"; import { get } from "svelte/store";
import type OptionPicker from "./OptionPicker.svelte";
export function deleteAccount(optPicker) { export function deleteAccount(optPicker: OptionPicker) {
optPicker.picker("What should we do with your files?",[ optPicker.picker("What should we do with your files?",[
{ {
name: "Delete my files", name: "Delete my files",
@ -56,7 +57,7 @@ export function deleteAccount(optPicker) {
}) })
} }
export function userChange(optPicker) { export function userChange(optPicker: OptionPicker) {
optPicker.picker("Change username",[ optPicker.picker("Change username",[
{ {
name: "New username", name: "New username",
@ -86,7 +87,7 @@ export function userChange(optPicker) {
}) })
} }
export function forgotPassword(optPicker) { export function forgotPassword(optPicker: OptionPicker) {
optPicker.picker("Forgot your password?",[ optPicker.picker("Forgot your password?",[
{ {
name: "Username", name: "Username",
@ -115,7 +116,7 @@ export function forgotPassword(optPicker) {
}) })
} }
export function emailPotentialRemove(optPicker) { export function emailPotentialRemove(optPicker: OptionPicker) {
optPicker.picker("What would you like to do?",[ optPicker.picker("What would you like to do?",[
{ {
name: "Set a new email", name: "Set a new email",
@ -148,7 +149,7 @@ export function emailPotentialRemove(optPicker) {
}) })
} }
export function emailChange(optPicker) { export function emailChange(optPicker: OptionPicker) {
optPicker.picker("Change email",[ optPicker.picker("Change email",[
{ {
name: "New email", name: "New email",
@ -177,7 +178,7 @@ export function emailChange(optPicker) {
}) })
} }
export function pwdChng(optPicker) { export function pwdChng(optPicker: OptionPicker) {
optPicker.picker("Change password",[ optPicker.picker("Change password",[
{ {
name: "New password", name: "New password",
@ -209,7 +210,7 @@ export function pwdChng(optPicker) {
}) })
} }
export function customcss(optPicker) { export function customcss(optPicker: OptionPicker) {
optPicker.picker("Set custom CSS",[ optPicker.picker("Set custom CSS",[
{ {
name: "Enter a file ID", name: "Enter a file ID",
@ -250,7 +251,7 @@ export function customcss(optPicker) {
} }
export function embedColor(optPicker) { export function embedColor(optPicker: OptionPicker) {
optPicker.picker("Set embed color",[ optPicker.picker("Set embed color",[
{ {
name: "FFFFFF", name: "FFFFFF",
@ -290,7 +291,7 @@ export function embedColor(optPicker) {
} }
export function embedSize(optPicker) { export function embedSize(optPicker: OptionPicker) {
optPicker.picker("Set embed image size",[ optPicker.picker("Set embed image size",[
{ {
name: "Large", name: "Large",

View file

@ -1,7 +1,8 @@
import { fetchAccountData, fetchFilePointers, account } from "../stores.mjs" import { fetchAccountData, fetchFilePointers, account } from "../stores"
import { get } from "svelte/store"; import { get } from "svelte/store";
import type OptionPicker from "./OptionPicker.svelte";
export function pwdReset(optPicker) { export function pwdReset(optPicker: OptionPicker) {
optPicker.picker("Reset password",[ optPicker.picker("Reset password",[
{ {
name: "Target user", name: "Target user",
@ -39,7 +40,7 @@ export function pwdReset(optPicker) {
}) })
} }
export function chgOwner(optPicker) { export function chgOwner(optPicker: OptionPicker) {
optPicker.picker("Transfer file ownership",[ optPicker.picker("Transfer file ownership",[
{ {
name: "File ID", name: "File ID",
@ -75,7 +76,7 @@ export function chgOwner(optPicker) {
}) })
} }
export function chgId(optPicker) { export function chgId(optPicker: OptionPicker) {
optPicker.picker("Change file ID",[ optPicker.picker("Change file ID",[
{ {
name: "Target file", name: "Target file",
@ -111,7 +112,7 @@ export function chgId(optPicker) {
}) })
} }
export function delFile(optPicker) { export function delFile(optPicker: OptionPicker) {
optPicker.picker("Delete file",[ optPicker.picker("Delete file",[
{ {
name: "File ID", name: "File ID",
@ -140,7 +141,7 @@ export function delFile(optPicker) {
}) })
} }
export function elevateUser(optPicker) { export function elevateUser(optPicker: OptionPicker) {
optPicker.picker("Elevate user",[ optPicker.picker("Elevate user",[
{ {
name: "Username", name: "Username",
@ -171,7 +172,7 @@ export function elevateUser(optPicker) {
// im really lazy so i just stole this from account.js // im really lazy so i just stole this from account.js
export function deleteAccount(optPicker) { export function deleteAccount(optPicker: OptionPicker) {
optPicker.picker("What should we do with the target account's files?",[ optPicker.picker("What should we do with the target account's files?",[
{ {
name: "Delete files", name: "Delete files",

View file

@ -1,5 +1,7 @@
import { fetchAccountData, fetchFilePointers, account } from "../stores.mjs" import { fetchAccountData, fetchFilePointers, account } from "../stores"
import { get } from "svelte/store"; import { get } from "svelte/store";
import type OptionPicker from "./OptionPicker.svelte"
import type { FilePointer } from "../../../server/lib/files";
export let options = { export let options = {
FV: [ FV: [
@ -51,7 +53,7 @@ export let options = {
] ]
} }
export function dfv(optPicker) { export function dfv(optPicker: OptionPicker) {
optPicker.picker("Default file visibility",options.FV).then((exp) => { optPicker.picker("Default file visibility",options.FV).then((exp) => {
if (exp && exp.selected) { if (exp && exp.selected) {
fetch(`/auth/dfv`,{method:"POST", body:JSON.stringify({ fetch(`/auth/dfv`,{method:"POST", body:JSON.stringify({
@ -68,21 +70,21 @@ export function dfv(optPicker) {
}) })
} }
export function update_all_files(optPicker) { export function update_all_files(optPicker: OptionPicker) {
optPicker.picker("You sure?",[ optPicker.picker("You sure?",[
{ {
name: "Yeah", name: "Yeah",
icon: "/static/assets/icons/update.svg", icon: "/static/assets/icons/update.svg",
description: `This will make all of your files ${get(account).defaultFileVisibility || "public"}`, description: `This will make all of your files ${get(account)?.defaultFileVisibility || "public"}`,
id: true id: true
} }
]).then((exp) => { ]).then((exp) => {
if (exp && exp.selected) { if (exp && exp.selected) {
fetch(`/files/manage`,{method:"POST", body:JSON.stringify({ fetch(`/files/manage`,{method:"POST", body:JSON.stringify({
target:get(account).files, target:get(account)?.files,
action: "changeFileVisibility", action: "changeFileVisibility",
value: get(account).defaultFileVisibility value: get(account)?.defaultFileVisibility
})}).then((response) => { })}).then((response) => {
if (response.status != 200) { if (response.status != 200) {
@ -95,7 +97,7 @@ export function update_all_files(optPicker) {
}) })
} }
export function fileOptions(optPicker,file) { export function fileOptions(optPicker: OptionPicker, file: FilePointer & {id:string}) {
optPicker.picker(file.filename,[ optPicker.picker(file.filename,[
{ {
name: file.tag ? "Remove tag" : "Tag file", name: file.tag ? "Remove tag" : "Tag file",

View file

@ -1,26 +1,26 @@
<script> <script lang="ts">
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, refreshNeeded } from "../stores.mjs"; import { account, fetchAccountData, serverStats, refreshNeeded } from "../stores";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import OptionPicker from "../prompts/OptionPicker.svelte"; import OptionPicker from "../prompts/OptionPicker.svelte";
import * as accOpts from "../prompts/account"; import * as accOpts from "../prompts/account";
import * as uplOpts from "../prompts/uploads"; import * as uplOpts from "../prompts/uploads";
import * as admOpts from "../prompts/admin"; import * as admOpts from "../prompts/admin";
let targetAction let targetAction: "login"|"create"
let inProgress let inProgress: boolean
let authError let authError:{status:number,message:string}|undefined
let pwErr let pwErr: HTMLDivElement
let optPicker; let optPicker: OptionPicker;
// lazy // lazy
let username let username: string
let password let password: string
let execute = () => { let execute = () => {
if (inProgress) return if (inProgress) return
@ -43,7 +43,7 @@
} }
}) })
} else { } else {
authError = null, username = "", password = ""; authError = undefined, username = "", password = "";
fetchAccountData(); fetchAccountData();
} }
}).catch(() => {}) }).catch(() => {})
@ -66,55 +66,7 @@
<Pulldown name="accounts"> <Pulldown name="accounts">
<OptionPicker bind:this={optPicker} /> <OptionPicker bind:this={optPicker} />
{#if Object.keys($account).length == 0} {#if $account}
<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>
{#if targetAction == "login"}
<button class="flavor" on:click={() => accOpts.forgotPassword(optPicker)}>I forgot my password</button>
{/if}
</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}}> <div class="loggedIn" transition:fade={{duration:200}}>
<h1> <h1>
Hey there, <span class="monospace">@{$account.username}</span> Hey there, <span class="monospace">@{$account.username}</span>
@ -131,7 +83,7 @@
<p>Change username</p> <p>Change username</p>
</button> </button>
<button on:click={() => ($account.email ? accOpts.emailPotentialRemove : accOpts.emailChange)(optPicker)}> <button on:click={() => ($account?.email ? accOpts.emailPotentialRemove : accOpts.emailChange)(optPicker)}>
<img src="/static/assets/icons/mail.svg" alt="change email"> <img src="/static/assets/icons/mail.svg" alt="change email">
<p>Change email{#if $account.email}<span class="monospaceText"><br />{$account.email}</span>{/if}</p> <p>Change email{#if $account.email}<span class="monospaceText"><br />{$account.email}</span>{/if}</p>
</button> </button>
@ -182,7 +134,7 @@
</button> </button>
{#if $refreshNeeded} {#if $refreshNeeded}
<button on:click={() => window.location.reload(true)} transition:fade={{duration: 200}}> <button on:click={() => window.location.reload()} transition:fade={{duration: 200}}>
<img src="/static/assets/icons/refresh.svg" alt="refresh"> <img src="/static/assets/icons/refresh.svg" alt="refresh">
<p>Refresh<span><br />Changes were made which require a refresh</span></p> <p>Refresh<span><br />Changes were made which require a refresh</span></p>
</button> </button>
@ -194,12 +146,12 @@
<button on:click={() => fetch(`/auth/logout_sessions`,{method:"POST"}).then(() => fetchAccountData())}> <button on:click={() => fetch(`/auth/logout_sessions`,{method:"POST"}).then(() => fetchAccountData())}>
<img src="/static/assets/icons/logout_all.svg" alt="logout_all"> <img src="/static/assets/icons/logout_all.svg" alt="logout_all">
<p>Log out all sessions<span><br />{$account.sessionCount} session(s) active</span></p> <p>Log out all sessions<span><br />{$account?.sessionCount} session(s) active</span></p>
</button> </button>
<button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}> <button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}>
<img src="/static/assets/icons/logout.svg" alt="logout"> <img src="/static/assets/icons/logout.svg" alt="logout">
<p>Log out<span><br />Session expires {new Date($account.sessionExpires).toLocaleDateString()}</span></p> <p>Log out<span><br />Session expires {new Date($account?.sessionExpires).toLocaleDateString()}</span></p>
</button> </button>
{#if $account.admin} {#if $account.admin}
@ -242,6 +194,50 @@
<p style="font-size:12px;color:#AAAAAA;text-align:center;" class="monospace"><br />{$account.id}</p> <p style="font-size:12px;color:#AAAAAA;text-align:center;" class="monospace"><br />{$account.id}</p>
</div> </div>
</div> </div>
{:else}
<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}>{@html inProgress ? "<span class=loader><i>•</i> <i>•</i> <i>•</i></span>" : (targetAction=="login" ? "Log in" : "Create account") }</button>
{#if targetAction == "login"}
<button class="flavor" on:click={() => accOpts.forgotPassword(optPicker)}>I forgot my password</button>
{/if}
</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>
{/if} {/if}
</Pulldown> </Pulldown>

View file

@ -1,13 +1,13 @@
<script> <script lang="ts">
import Pulldown from "./Pulldown.svelte"; import Pulldown from "./Pulldown.svelte";
import { account, fetchFilePointers, files, pulldownManager } from "../stores.mjs"; import { account, fetchFilePointers, files, pulldownManager } from "../stores.js";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { flip } from "svelte/animate"; import { flip } from "svelte/animate";
import { fileOptions } from "../prompts/uploads"; import { fileOptions } from "../prompts/uploads";
import OptionPicker from "../prompts/OptionPicker.svelte"; import OptionPicker from "../prompts/OptionPicker.svelte";
let picker; let picker: OptionPicker;
let query = ""; let query = "";
fetchFilePointers(); fetchFilePointers();
@ -17,48 +17,47 @@
<OptionPicker bind:this={picker} /> <OptionPicker bind:this={picker} />
{#if !$account.username} {#if $account?.username}<div class="loggedIn">
<input type="text" placeholder={`Search ${$files.length} file(s)`} class="searchBar" bind:value={query}>
<div class="fileList">
<!-- Probably wildly inefficient but who cares, I just wanna get this over with -->
{#each $files.filter(f => f&&(f.filename.toLowerCase().includes(query.toLowerCase()) || f.id.toLowerCase().includes(query.toLowerCase()) || f.tag?.includes(query.toLowerCase()))) as file (file.id)}
<div class="flFile" transition:fade={{duration:200}} animate:flip={{duration:200}}>
<button class="hitbox" on:click={() => window.open(`/download/${file.id}`)}></button> <!-- this is bad, but I'm lazy -->
<div class="flexCont">
<div class="fileInfo">
<h2>{file.filename}</h2>
<p class="detail">
<img src="/static/assets/icons/{file.visibility || "public"}.svg" alt={file.visibility||"public"} />&nbsp;
<span class="number">{file.id}</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="cd">{file.mime.split(";")[0]}</span>
{#if file.reserved}
<br />
<img src="/static/assets/icons/update.svg" alt="uploading"/>&nbsp;
Uploading...
{/if}
{#if file.tag}
<br />
<img src="/static/assets/icons/tag.svg" alt="tag"/>&nbsp;
<span class="cd">{file.tag}</span>
{/if}
</p>
</div>
<button class="more" on:click={() => fileOptions(picker, file)}>
<img src="/static/assets/icons/more.svg" alt="more" />
</button>
</div>
</div>
{/each}
</div>
</div>
{:else}
<div class="notLoggedIn"> <div class="notLoggedIn">
<div style:height="10px" /> <div style:height="10px" />
<p class="flavor">Log in to view uploads</p> <p class="flavor">Log in to view uploads</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>
{:else}
<div class="loggedIn">
<input type="text" placeholder={`Search ${$files.length} file(s)`} class="searchBar" bind:value={query}>
<div class="fileList">
<!-- Probably wildly inefficient but who cares, I just wanna get this over with -->
{#each $files.filter(f => f&&(f.filename.toLowerCase().includes(query.toLowerCase()) || f.id.toLowerCase().includes(query.toLowerCase()) || f.tag?.includes(query.toLowerCase()))) as file (file.id)}
<div class="flFile" transition:fade={{duration:200}} animate:flip={{duration:200}}>
<button class="hitbox" on:click={window.open(`/download/${file.id}`)}></button> <!-- this is bad, but I'm lazy -->
<div class="flexCont">
<div class="fileInfo">
<h2>{file.filename}</h2>
<p class="detail">
<img src="/static/assets/icons/{file.visibility || "public"}.svg" alt={file.visibility||"public"} />&nbsp;
<span class="number">{file.id}</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="cd">{file.mime.split(";")[0]}</span>
{#if file.reserved}
<br />
<img src="/static/assets/icons/update.svg" alt="uploading"/>&nbsp;
Uploading...
{/if}
{#if file.tag}
<br />
<img src="/static/assets/icons/tag.svg" alt="tag"/>&nbsp;
<span class="cd">{file.tag}</span>
{/if}
</p>
</div>
<button class="more" on:click={fileOptions(picker, file)}>
<img src="/static/assets/icons/more.svg" alt="more" />
</button>
</div>
</div>
{/each}
</div>
</div>
{/if} {/if}
</Pulldown> </Pulldown>

View file

@ -1,13 +1,13 @@
<script> <script lang=ts>
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
export let name; export let name: string;
</script> </script>
<div <div
class="pulldown_display" class="pulldown_display"
name={name} data-name={name}
transition:fade={{duration:150}} transition:fade={{duration:150}}
> >
<slot /> <slot />

View file

@ -1,17 +1,22 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
//import type Pulldown from "./pulldowns/Pulldown.svelte"
import type { SvelteComponent } from "svelte"
import type { Account } from "../../server/lib/accounts"
import type cfg from "../../../config.json"
import type { FilePointer } from "../../server/lib/files"
export let refreshNeeded = writable(false) export let refreshNeeded = writable(false)
export let pulldownManager = writable(0) export let pulldownManager = writable<SvelteComponent>()
export let account = writable({}) export let account = writable<Account & {sessionCount: number, sessionExpires: number}|undefined>()
export let serverStats = writable({}) export let serverStats = writable<typeof cfg & {version: string, files: number} | undefined>()
export let files = writable([]) export let files = writable<(FilePointer & {id:string})[]>([])
export let fetchAccountData = function() { export let fetchAccountData = function() {
fetch("/auth/me").then(async (response) => { fetch("/auth/me").then(async (response) => {
if (response.status == 200) { if (response.status == 200) {
account.set(await response.json()) account.set(await response.json())
} else { } else {
account.set({}) account.set(undefined)
} }
}).catch((err) => { console.error(err) }) }).catch((err) => { console.error(err) })
} }

View file

@ -1,20 +0,0 @@
import { circIn, circOut } from "svelte/easing"
export function _void(node, { duration, easingFunc, op, prop, rTarg }) {
let rect = node.getBoundingClientRect()
return {
duration: duration||300,
css: t => {
let eased = (easingFunc || circIn)(t)
return `
white-space: nowrap;
${prop||"height"}: ${(eased)*(rect[rTarg||prop||"height"])}px;
padding: 0px;
opacity:${eased};
overflow: clip;
`
}
}
}

View file

@ -0,0 +1,23 @@
import { circIn, circOut } from "svelte/easing"
export function _void(
node: HTMLElement,
options?: { duration?:number, easingFunc?: (a:number)=>number, prop?:string, rTarg?: "height"|"width"}
) {
const { duration = 300, easingFunc = circIn, prop, rTarg } = options ?? {}
let rect = node.getBoundingClientRect()
return {
duration,
css: (t: number) => {
let eased = easingFunc(t)
return `
white-space: nowrap;
${prop||"height"}: ${(eased)*(rect[rTarg || (prop && prop in rect) ? prop as keyof Omit<DOMRect, "toJSON"> : "height"])}px;
padding: 0px;
opacity:${eased};
overflow: clip;
`
}
}
}

View file

@ -1,18 +0,0 @@
import { circIn, circOut } from "svelte/easing"
export function padding_scaleY(node, { duration, easingFunc, padY, padX, op }) {
let rect = node.getBoundingClientRect()
return {
duration: duration||300,
css: t => {
let eased = (easingFunc || circOut)(t)
return `
height: ${eased*(rect.height-(padY||0))}px;
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
${op ? `opacity: ${eased};` : ""}
`
}
}
}

View file

@ -0,0 +1,21 @@
import { circIn, circOut } from "svelte/easing"
function padding_scaleY(node: HTMLElement, options?: { duration?: number, easingFunc?: (a: number) => number, padY?: number, padX?: number, op?: boolean }) {
const { duration = 300, easingFunc = circOut, padY, padX, op } = options ?? {}
let rect = node.getBoundingClientRect()
return {
duration,
css: (t:number) => {
let eased = easingFunc(t)
return `
height: ${eased*(rect.height-(padY||0))}px;
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
${op ? `opacity: ${eased};` : ""}
`
}
}
}
export {padding_scaleY}

View file

@ -1,56 +1,35 @@
<script> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { circIn, circOut } from "svelte/easing" import { circOut } from "svelte/easing"
import { fade } from "svelte/transition";
import { _void } from "../transition/_void" import { _void } from "../transition/_void"
let uploadTypes = { enum UploadTypes {
files: 1, None,
clone: 2 Files,
Clone
} }
let uploadType = undefined let uploadType: UploadTypes = UploadTypes.None
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();
// file upload // file upload
let files: FileList | undefined
/** $: if (files) {
* @type HTMLInputElement [...files].forEach(file=>dispatch("addFiles", file))
*/ uploadType = UploadTypes.None
let fileUpload;
$: {
if (fileUpload) {
fileUpload.addEventListener("change",() => {
dispatch("addFiles",{
type: "upload",
files: Array.from(fileUpload.files)
})
uploadType = undefined
})
}
} }
// file clone // file clone
/** let cloneUrlTextbox: HTMLInputElement;
* @type HTMLButtonElement let cloneForm: HTMLFormElement;
*/
let cloneButton;
/**
* @type HTMLInputElement
*/
let cloneUrlTextbox;
$: { $: {
if (cloneButton && cloneUrlTextbox) { if (cloneForm && cloneUrlTextbox) {
cloneButton.addEventListener("click",() => { cloneForm.addEventListener("submit",(e) => {
e.preventDefault()
if (cloneUrlTextbox.value) { if (cloneUrlTextbox.value) {
dispatch("addFiles",{ dispatch("addFiles",cloneUrlTextbox.value)
type: "clone", uploadType = UploadTypes.None;
url: cloneUrlTextbox.value
})
uploadType = undefined;
} else { } else {
cloneUrlTextbox.animate([ cloneUrlTextbox.animate([
{"transform":"translateX(0px)"}, {"transform":"translateX(0px)"},
@ -68,26 +47,26 @@
<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 == UploadTypes.None}
<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:files={files}>
</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}}> <form id="file_add_btns" out:_void in:_void={{easingFunc:circOut}} bind:this={cloneForm}>
<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> <input type="submit" value="add file" style:flex-basis="30%">
</div> </form>
{/if} {/if}
{/if} {/if}
</div> </div>

1
src/svelte/global.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="svelte" />

18
src/svelte/tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["**/*"],
"compilerOptions": {
"target": "ESNext",
"outDir": "../../dist/static/vite",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
"moduleResolution": "bundler"
},
"references": [
{ "path": "../../tsconfig.json" }
]
}

View file

@ -1,5 +1,6 @@
import { defineConfig } from "vite" import { defineConfig } from "vite"
import { svelte } from "@sveltejs/vite-plugin-svelte" import { svelte } from "@sveltejs/vite-plugin-svelte"
import autoPreprocess from "svelte-preprocess"
import { resolve } from "path" import { resolve } from "path"
export default defineConfig({ export default defineConfig({
root: "./src", root: "./src",
@ -14,5 +15,7 @@ export default defineConfig({
}, },
}, },
}, },
plugins: [svelte({})], plugins: [svelte({
preprocess: autoPreprocess()
})],
}) })