Convert multiple pages into SPA
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
BIN
res/static/img/carina.webp
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
res/static/img/catspaw.webp
Normal file
After Width: | Height: | Size: 361 KiB |
BIN
res/static/img/fnx_logo.png
Normal file
After Width: | Height: | Size: 777 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -58,12 +58,6 @@ a>svg {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
/* This makes sure that no scrollbar shows up when the menu is open on small screens*/
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
@@ -76,12 +70,6 @@ body {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.checkers {
|
||||
background-image: var(--background_pattern);
|
||||
background-color: var(--background_pattern_color);
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
text-align: center;
|
||||
@@ -90,24 +78,16 @@ footer {
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url("/res/img/nebula.webp");
|
||||
background-color: var(--background_color);
|
||||
background-blend-mode: luminosity;
|
||||
box-shadow: inset 0 0 10px -4px var(--shadow_color);
|
||||
border-radius: 8px;
|
||||
margin: 16px;
|
||||
background-color: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-top: 1px solid var(--separator);
|
||||
}
|
||||
|
||||
footer>.footer_content {
|
||||
background: var(--body_background);
|
||||
color: var(--body_text_color);
|
||||
display: inline-block;
|
||||
width: 1000px;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 120px 0 60px 0;
|
||||
}
|
||||
|
||||
header>h1 {
|
||||
@@ -173,52 +153,17 @@ pre>code {
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.page_body {
|
||||
position: relative;
|
||||
right: 0;
|
||||
height: auto;
|
||||
left: 0;
|
||||
margin-left: 300px;
|
||||
min-width: 300px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
/* Center the header and body */
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition: margin 0.5s;
|
||||
}
|
||||
|
||||
.page_content {
|
||||
background: var(--body_background);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page_content,
|
||||
.page_margins,
|
||||
footer {
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
@media (max-width: 1000px) {
|
||||
.page_navigation {
|
||||
left: -300px;
|
||||
}
|
||||
|
||||
.page_body {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page_content,
|
||||
.page_margins,
|
||||
footer {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
header>h1 {
|
||||
/* We want the header text to appear below the menu button, so the top
|
||||
margin needs to be fairly large when the screen is small */
|
||||
@@ -537,6 +482,12 @@ input[type="color"] {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.button.flat {
|
||||
background: none;
|
||||
color: var(--body_text_color);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
.button:hover,
|
||||
input[type="submit"]:hover,
|
||||
|
@@ -1,34 +0,0 @@
|
||||
{{define "admin"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{if and .Authenticated .User.IsAdmin}}
|
||||
<head>
|
||||
{{template "meta_tags" "Administrator panel"}}
|
||||
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
<script defer src='/res/svelte/admin_panel.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "menu" .}}
|
||||
<div id="page_body" class="page_body"></div>
|
||||
</body>
|
||||
{{else}}
|
||||
<head>
|
||||
{{template "meta_tags" "Administrator panel"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "page_top" .}}
|
||||
<header>
|
||||
<h1>Admin Panel</h1>
|
||||
</header>
|
||||
<div id="page_content" class="page_content">
|
||||
;-)
|
||||
</div>
|
||||
{{template "page_bottom" .}}
|
||||
</body>
|
||||
{{end}}
|
||||
</html>
|
||||
{{end}}
|
@@ -1,152 +0,0 @@
|
||||
{{define "appearance"}}<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "meta_tags" "Appearance settings"}}
|
||||
<link id="stylesheet_theme_2" rel="stylesheet" type="text/css" href="/theme.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "page_top" .}}
|
||||
<header>
|
||||
<h1>Change website appearance</h1>
|
||||
</header>
|
||||
<div id="page_content" class="page_content">
|
||||
<section>
|
||||
<p>
|
||||
You can change how pixeldrain looks! Your theme choice will
|
||||
be saved in a cookie.
|
||||
</p>
|
||||
<h2>Theme</h2>
|
||||
<input type="radio" id="style_nord" name="style"><label for="style_nord">Nord</label>
|
||||
(Inspired by <a href="https://www.nordtheme.com/" target="_blank">Nord</a>)
|
||||
<br/>
|
||||
Dynamic theme, changes based on operating system settings. Here you can choose a specific variant:
|
||||
<br/>
|
||||
<input type="radio" id="style_nord_dark" name="style"><label for="style_nord_dark">Nord dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_nord_light" name="style"><label for="style_nord_light">Nord light</label>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized" name="style"><label for="style_solarized">Solarized</label>
|
||||
(Inspired by <a href="https://ethanschoonover.com/solarized/" target="_blank">Solarized</a>)
|
||||
<br/>
|
||||
Dynamic theme, changes based on operating system settings. Here you can choose a specific variant:
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized_dark" name="style"><label for="style_solarized_dark">Solarized dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized_light" name="style"><label for="style_solarized_light">Solarized light</label>
|
||||
<br/>
|
||||
<!-- <br/> -->
|
||||
<!-- <input type="radio" id="style_adwaita" name="style"><label for="style_adwaita">Adwaita</label><br/> -->
|
||||
<br/>
|
||||
<input type="radio" id="style_purple_drain" name="style"><label for="style_purple_drain">Purple drain</label>
|
||||
<br/>
|
||||
Classic 2022 style, with purple gradients
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_classic" name="style"><label for="style_classic">Pixeldrain classic (gray)</label>
|
||||
<br/>
|
||||
Classic pre-2020 pixeldrain style, dark gray
|
||||
<br/>
|
||||
<br/>
|
||||
Other (experimental) themes
|
||||
<br/>
|
||||
<input type="radio" id="style_maroon" name="style"><label for="style_maroon">Maroon Style</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_hacker" name="style"><label for="style_hacker">Hacker Style</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_canta" name="style"><label for="style_canta">Canta Style</label>
|
||||
(Inspired by <a href="https://github.com/vinceliuice/Canta-theme" target="_blank">Canta GTK</a>)
|
||||
<br/>
|
||||
<input type="radio" id="style_skeuos" name="style"><label for="style_skeuos">Skeuos Style</label>
|
||||
(Inspired by <a href="https://www.gnome-look.org/p/1441725/" target="_blank">Skeuos GTK</a>)
|
||||
<br/>
|
||||
<input type="radio" id="style_sweet" name="style"><label for="style_sweet">Sweet</label>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita" name="style"><label for="style_adwaita">Adwaita (dynamic)</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita_dark" name="style"><label for="style_adwaita_dark">Adwaita dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita_light" name="style"><label for="style_adwaita_light">Adwaita light</label>
|
||||
<br/><br/>
|
||||
<input type="radio" id="style_pixeldrain98" name="style"><label for="style_pixeldrain98">Pixeldrain 98</label>
|
||||
|
||||
<h2>Hue</h2>
|
||||
<p>
|
||||
Many themes support custom hues. The hue does not change the
|
||||
contrast of the theme, only the color itself.
|
||||
</p>
|
||||
<input type="radio" id="hue_default" name="hue"><label for="hue_default">Default</label><br/>
|
||||
<input type="radio" id="hue_354" name="hue"><label for="hue_354">Red</label><br/>
|
||||
<input type="radio" id="hue_14" name="hue"><label for="hue_14">Orange</label><br/>
|
||||
<input type="radio" id="hue_40" name="hue"><label for="hue_40">Yellow</label><br/>
|
||||
<input type="radio" id="hue_92" name="hue"><label for="hue_92">Green</label><br/>
|
||||
<input type="radio" id="hue_311" name="hue"><label for="hue_311">Purple</label><br/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function get_cookie(cname) {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i <ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
let style = get_cookie("style")
|
||||
let hue = get_cookie("hue")
|
||||
|
||||
// Style selector
|
||||
document.getElementsByName("style").forEach(function(elem) {
|
||||
elem.addEventListener("change", e => {
|
||||
style = e.target.id.substring(6)
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (10 * 365 * 24 * 60 * 60 * 1000));
|
||||
document.cookie = "style="+style+"; expires=" + date.toUTCString() + "; path=/"
|
||||
|
||||
reload_sheet()
|
||||
})
|
||||
});
|
||||
document.getElementsByName("hue").forEach(function(elem) {
|
||||
elem.addEventListener("change", e => {
|
||||
hue = e.target.id.substring(4)
|
||||
if (hue === "default") {
|
||||
hue = -1
|
||||
document.cookie = "hue=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (10 * 365 * 24 * 60 * 60 * 1000));
|
||||
document.cookie = "hue="+hue+"; expires=" + date.toUTCString() + "; path=/"
|
||||
|
||||
reload_sheet()
|
||||
})
|
||||
});
|
||||
|
||||
function reload_sheet() {
|
||||
let stylesheet1 = document.getElementById("stylesheet_theme")
|
||||
let stylesheet2 = document.getElementById("stylesheet_theme_2")
|
||||
|
||||
// First load the sheet in the secondary tag, wait for it to load,
|
||||
// and replace the original sheet when it has finished loading
|
||||
stylesheet2.href= "/theme.css?style="+style+"&hue="+hue
|
||||
stylesheet2.onload = e => {
|
||||
stylesheet1.href= "/theme.css?style="+style+"&hue="+hue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{template "page_bottom" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
@@ -1,19 +0,0 @@
|
||||
{{define "home"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "meta_tags" "Cloud storage and data transfer services"}}
|
||||
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.user = {{.User}};
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
<script defer src='/res/svelte/home_page.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "menu" .}}
|
||||
<div id="page_body" class="page_body"></div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
@@ -1,19 +0,0 @@
|
||||
{{define "login"}}<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "meta_tags" "Login" }}
|
||||
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.user = {{.User}};
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
<script defer src='/res/svelte/login.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "menu" .}}
|
||||
<div id="page_body" class="page_body"></div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
@@ -1,19 +0,0 @@
|
||||
{{define "speedtest"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "meta_tags" "Speedtest"}}
|
||||
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.user = {{.User}};
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
<script defer src='/res/svelte/speedtest.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "menu" .}}
|
||||
<div id="page_body" class="page_body"></div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
@@ -1,19 +0,0 @@
|
||||
{{define "user_home"}}<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "meta_tags" .User.Username }}
|
||||
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.user = {{.User}};
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
<script defer src='/res/svelte/user_home.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "menu" .}}
|
||||
<div id="page_body" class="page_body"></div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
@@ -1,4 +1,4 @@
|
||||
{{define "filesystem"}}
|
||||
{{define "wrap"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
{{ template "opengraph" .OGData }}
|
||||
<script>
|
||||
window.initial_node = {{.Other}};
|
||||
window.user = {{.User}};
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.server_hostname = "{{.Hostname}}";
|
||||
</script>
|
||||
|
||||
<script defer src='/res/svelte/filesystem.js?v{{cacheID}}'></script>
|
||||
<script defer src='/res/svelte/wrap.js?v{{cacheID}}'></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
211
svelte/package-lock.json
generated
@@ -11,7 +11,6 @@
|
||||
"behave-js": "^1.5.0",
|
||||
"chart.js": "^4.4.6",
|
||||
"country-data-list": "^1.4.0",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"pure-color": "^1.3.0",
|
||||
"rollup-plugin-includepaths": "^0.2.4",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
@@ -76,6 +75,7 @@
|
||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.0",
|
||||
@@ -1652,191 +1652,6 @@
|
||||
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz",
|
||||
"integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"workspaces": [
|
||||
"e2e/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "0.1.80",
|
||||
"@napi-rs/canvas-darwin-arm64": "0.1.80",
|
||||
"@napi-rs/canvas-darwin-x64": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.80",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-x64-musl": "0.1.80",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.80"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
|
||||
"integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
|
||||
"integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
|
||||
"integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
|
||||
"integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
|
||||
"integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
|
||||
"integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
|
||||
"integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -2465,6 +2280,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001669",
|
||||
"electron-to-chromium": "^1.5.41",
|
||||
@@ -2486,9 +2302,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001712",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
|
||||
"integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==",
|
||||
"version": "1.0.30001749",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
|
||||
"integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3166,18 +2982,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pdfjs-dist": {
|
||||
"version": "5.4.149",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.149.tgz",
|
||||
"integrity": "sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=20.16.0 || >=22.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas": "^0.1.77"
|
||||
}
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
|
||||
@@ -3371,6 +3175,7 @@
|
||||
"integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
@@ -3703,6 +3508,7 @@
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
|
||||
"integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||
@@ -3832,7 +3638,8 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.3",
|
||||
|
@@ -11,12 +11,7 @@ const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
const builddir = "../res/static/svelte"
|
||||
export default [
|
||||
"filesystem",
|
||||
"user_home",
|
||||
"admin_panel",
|
||||
"home_page",
|
||||
"speedtest",
|
||||
"login",
|
||||
"wrap",
|
||||
].map((name, index) => ({
|
||||
input: `src/${name}.js`,
|
||||
output: {
|
||||
|
@@ -1,8 +0,0 @@
|
||||
import App from './admin_panel/Router.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("page_body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import AbuseReport from "./AbuseReport.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let reports = []
|
||||
@@ -12,7 +12,7 @@ let endPicker
|
||||
let tab = "pending"
|
||||
|
||||
const get_reports = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
|
||||
// Remove refresh timeout if there is one
|
||||
clearTimeout(refresh_timeout)
|
||||
@@ -70,7 +70,7 @@ const get_reports = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -156,8 +156,6 @@ onMount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div>Reports: {reports.length}</div>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import EmailReportersTable from "./EmailReportersTable.svelte";
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
type Reporter = {
|
||||
from_address: string,
|
||||
@@ -17,7 +17,6 @@ type Reporter = {
|
||||
last_message_html: string,
|
||||
}
|
||||
|
||||
let loading = true
|
||||
let reporters: Reporter[] = []
|
||||
$: reporters_pending = reporters.reduce((acc, val) => {
|
||||
if (val.status === "pending") {
|
||||
@@ -39,7 +38,7 @@ $: reporters_rejected = reporters.reduce((acc, val) => {
|
||||
}, [])
|
||||
|
||||
const get_reporters = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(get_endpoint()+"/admin/abuse_reporter");
|
||||
if(resp.status >= 400) {
|
||||
@@ -49,7 +48,7 @@ const get_reporters = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,8 +136,6 @@ const delete_reporter = async (reporter: Reporter) => {
|
||||
onMount(get_reporters);
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div class="toolbar_spacer"></div>
|
||||
|
@@ -276,24 +276,12 @@ onDestroy(() => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>File statistics (per file)</td>
|
||||
<td>{status.stats_watcher_threads}</td>
|
||||
<td>{status.stats_watcher_listeners}</td>
|
||||
<td>{(status.stats_watcher_listeners / status.stats_watcher_threads).toPrecision(3)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Filesystem statistics (per file)</td>
|
||||
<td>{status.filesystem_watcher_threads}</td>
|
||||
<td>{status.filesystem_watcher_listeners}</td>
|
||||
<td>{(status.filesystem_watcher_listeners / status.filesystem_watcher_threads).toPrecision(3)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rate limits (per IP)</td>
|
||||
<td>{status.rate_limit_watcher_threads}</td>
|
||||
<td>{status.rate_limit_watcher_listeners}</td>
|
||||
<td>{(status.rate_limit_watcher_listeners / status.rate_limit_watcher_threads).toPrecision(3)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Downloads (per IP)</td>
|
||||
<td>{status.download_clients}</td>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import { loading_finish } from "lib/Loading";
|
||||
|
||||
const abuse_types = [
|
||||
"copyright",
|
||||
@@ -15,7 +15,6 @@ const abuse_types = [
|
||||
"revenge_porn",
|
||||
]
|
||||
|
||||
let loading = true
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
|
||||
@@ -25,7 +24,7 @@ let new_ban_address
|
||||
let new_ban_reason = abuse_types[0]
|
||||
|
||||
const get_bans = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/admin/ip_ban");
|
||||
if(resp.status >= 400) {
|
||||
@@ -39,7 +38,7 @@ const get_bans = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +94,6 @@ const delete_ban = async (addr) => {
|
||||
onMount(get_bans);
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<div class="toolbar">
|
||||
<div class="toolbar_label">
|
||||
|
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import SortableTable, { FieldType } from "layout/SortableTable.svelte";
|
||||
import { country_name, get_admin_invoices, type Invoice } from "lib/AdminAPI";
|
||||
import PayPalVat from "./PayPalVAT.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let invoices: Invoice[] = []
|
||||
|
||||
let year = 0
|
||||
@@ -62,7 +61,7 @@ const obj_to_list_eu = (obj: {[id: string]: Total}) => {
|
||||
}
|
||||
|
||||
const get_invoices = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await get_admin_invoices(year, month)
|
||||
|
||||
@@ -92,7 +91,7 @@ const get_invoices = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,8 +157,6 @@ let records_hidden = 0
|
||||
let invoices_filtered: Invoice[] = []
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h3>{year + "-" + ("00"+(month)).slice(-2)}</h3>
|
||||
<div class="toolbar">
|
||||
|
@@ -2,11 +2,10 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import { mollie_proxy_call } from "./MollieAPI";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { loading_start } from "lib/Loading";
|
||||
|
||||
export let settlement = {}
|
||||
let loading = true
|
||||
let payments = []
|
||||
|
||||
let per_country = {}
|
||||
@@ -49,7 +48,7 @@ const load_all_payments = async (settlement_id) => {
|
||||
}
|
||||
|
||||
const get_payments = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
payments = await load_all_payments(settlement.id)
|
||||
|
||||
@@ -84,15 +83,13 @@ const get_payments = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
onMount(get_payments);
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<h3>Accounting information</h3>
|
||||
|
||||
{#if per_country.NL}
|
||||
|
@@ -2,17 +2,16 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import MollieSettlement from "./MollieSettlement.svelte";
|
||||
import { mollie_proxy_call } from "./MollieAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let response = {}
|
||||
let settlements = []
|
||||
|
||||
const get_settlements = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
const req = await mollie_proxy_call("settlements?limit=250");
|
||||
if(req.status >= 400) {
|
||||
@@ -23,15 +22,13 @@ const get_settlements = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
onMount(get_settlements);
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
{#each settlements as row (row.id)}
|
||||
<Expandable click_expand>
|
||||
|
@@ -2,10 +2,9 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let response = {}
|
||||
let payments = []
|
||||
|
||||
@@ -40,8 +39,8 @@ const get_payments = async () => {
|
||||
fee: 0,
|
||||
}
|
||||
payments = []
|
||||
loading = true;
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
await get_page("https://api.mollie.com/v2/payments?limit=250")
|
||||
|
||||
@@ -76,7 +75,7 @@ const get_payments = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +117,6 @@ onMount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div>Payments: {payments.length}</div>
|
||||
|
@@ -2,19 +2,18 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Button from "layout/Button.svelte"
|
||||
import UserFiles from "./UserFiles.svelte";
|
||||
import BanDetails from "./BanDetails.svelte";
|
||||
import UserLists from "./UserLists.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
let expanded = false
|
||||
|
||||
const get_bans = async () => {
|
||||
loading = true;
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/admin/user_ban");
|
||||
if(resp.status >= 400) {
|
||||
@@ -28,7 +27,7 @@ const get_bans = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,8 +104,6 @@ const block_all_files = async (row, reason) => {
|
||||
onMount(get_bans);
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<div class="toolbar">
|
||||
<div class="toolbar_label">
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let user_id = ""
|
||||
let files = []
|
||||
let loading = true
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/files",
|
||||
@@ -30,7 +30,7 @@ export const reload = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,6 @@ const sort = (field) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<div class="table_scroll">
|
||||
<table>
|
||||
<thead>
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<script>
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
export let user_id = ""
|
||||
let lists = []
|
||||
let loading = true
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/lists",
|
||||
@@ -28,13 +28,11 @@ export const reload = async () => {
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
} finally {
|
||||
loading = false;
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<div class="table_scroll">
|
||||
<table>
|
||||
<thead>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fs_encode_path, node_is_shared } from "./FilesystemAPI";
|
||||
import { fs_encode_path, node_is_shared } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
@@ -9,8 +9,7 @@ export let nav: FSNavigator
|
||||
{#each $nav.path as node, i (node.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(node.path)}
|
||||
class="breadcrumb button"
|
||||
class:button_highlight={$nav.base_index === i}
|
||||
class="breadcrumb button flat"
|
||||
on:click|preventDefault={() => {nav.navigate(node.path, true)}}
|
||||
>
|
||||
{#if node.abuse_type !== undefined}
|
||||
@@ -22,19 +21,24 @@ export let nav: FSNavigator
|
||||
{node.name}
|
||||
</div>
|
||||
</a>
|
||||
{#if $nav.base_index !== i}
|
||||
<i class="icon">chevron_right</i>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.breadcrumbs {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: left;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
background: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-bottom: 1px solid var(--separator);
|
||||
}
|
||||
.breadcrumb {
|
||||
min-width: 1em;
|
||||
@@ -42,6 +46,8 @@ export let nav: FSNavigator
|
||||
word-break: break-all;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
background-color: unset;
|
||||
box-shadow: none;
|
||||
}
|
||||
.node_name {
|
||||
max-width: 20vw;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import Chart from "util/Chart.svelte";
|
||||
import { formatDataVolume, formatDate, formatThousands } from "util/Formatting";
|
||||
import Modal from "util/Modal.svelte";
|
||||
import { fs_path_url, fs_share_hotlink_url, fs_share_url, fs_timeseries, type FSNode } from "./FilesystemAPI";
|
||||
import { fs_path_url, fs_share_hotlink_url, fs_share_url, fs_timeseries, type FSNode } from "lib/FilesystemAPI";
|
||||
import { color_by_name } from "util/Util.svelte";
|
||||
import { tick } from "svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { fs_get_node, fs_encode_path, fs_split_path } from "./FilesystemAPI";
|
||||
import type { FSNode, FSPath, FSPermissions, FSContext } from "./FilesystemAPI";
|
||||
import type { Writable } from "svelte/store"
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { fs_get_node, fs_encode_path, fs_split_path } from "../lib/FilesystemAPI";
|
||||
import type { FSNode, FSPath, FSPermissions, FSContext } from "../lib/FilesystemAPI";
|
||||
|
||||
export class FSNavigator {
|
||||
// Parts of the raw API response
|
||||
@@ -22,27 +22,16 @@ export class FSNavigator {
|
||||
|
||||
constructor(history_enabled = true) {
|
||||
this.history_enabled = history_enabled
|
||||
}
|
||||
|
||||
// If history logging is enabled we capture the popstate event, which
|
||||
// fires when the user uses the back and forward buttons in the browser.
|
||||
// Instead of reloading the page we use the navigator to navigate to the
|
||||
// new page
|
||||
if (history_enabled) {
|
||||
window.addEventListener("popstate", () => {
|
||||
// The popstate event can be used to listen for navigation events. Register
|
||||
// this event listener on the <svelte:window> in the parent element. When
|
||||
// the user presses the back or forward buttons in the browser we'll catch
|
||||
// the event and navigate to the proper directory
|
||||
popstate = (e: PopStateEvent) => {
|
||||
// Get the part of the URL after the fs root and navigate to it
|
||||
const path = document.location.pathname.replace("/d/", "")
|
||||
this.navigate(decodeURIComponent(path), false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If you set the loading property to a boolean writable store the navigator
|
||||
// will use it to publish its loading states
|
||||
loading: Writable<boolean> | null = null
|
||||
set_loading = (b: boolean) => {
|
||||
if (this.loading !== null) {
|
||||
this.loading.set(b)
|
||||
}
|
||||
const path = window.location.pathname.replace(/^\/d/, "")
|
||||
this.navigate(decodeURI(path), false)
|
||||
}
|
||||
|
||||
// The FSNavigator acts as a svelte store. This allows for DOM reactivity.
|
||||
@@ -72,7 +61,7 @@ export class FSNavigator {
|
||||
console.debug("Navigating to path", path, push_history)
|
||||
|
||||
try {
|
||||
this.set_loading(true)
|
||||
loading_start()
|
||||
const resp = await fs_get_node(path)
|
||||
this.open_node(resp, push_history)
|
||||
} catch (err: any) {
|
||||
@@ -89,7 +78,7 @@ export class FSNavigator {
|
||||
alert("Error: " + err)
|
||||
}
|
||||
} finally {
|
||||
this.set_loading(false)
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +97,7 @@ export class FSNavigator {
|
||||
// we still replace the URL with replaceState. This way the user is not
|
||||
// greeted to a 404 page when refreshing after renaming a file
|
||||
if (this.history_enabled) {
|
||||
window.document.title = node.path[node.base_index].name + " ~ pixeldrain"
|
||||
window.document.title = node.path[node.base_index].name + " / FNX"
|
||||
const url = "/d" + fs_encode_path(node.path[node.base_index].path) + window.location.hash
|
||||
if (push_history) {
|
||||
window.history.pushState({}, window.document.title, url)
|
||||
@@ -189,14 +178,14 @@ export class FSNavigator {
|
||||
|
||||
let siblings: Array<FSNode>
|
||||
try {
|
||||
this.set_loading(true)
|
||||
loading_start()
|
||||
siblings = await this.get_siblings()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
return
|
||||
} finally {
|
||||
this.set_loading(false)
|
||||
loading_finish()
|
||||
}
|
||||
|
||||
let next_sibling: FSNode | null = null
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatThousands } from "util/Formatting"
|
||||
import { fs_path_url } from "./FilesystemAPI";
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
@@ -104,7 +104,7 @@ const close_socket = () => {
|
||||
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="label">Transfer used</div>
|
||||
<div class="label">Egress</div>
|
||||
<div class="stat">
|
||||
{loading ? "Loading..." : formatDataVolume(transfer_used, 3)}
|
||||
</div>
|
||||
@@ -140,18 +140,11 @@ const close_socket = () => {
|
||||
text-align: center;
|
||||
}
|
||||
.label {
|
||||
padding-left: 0.5em;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
line-height: 1em;
|
||||
}
|
||||
.stat {
|
||||
line-height: 1.2em;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.label {
|
||||
text-align: center;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,20 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import EditWindow from "./edit_window/EditWindow.svelte";
|
||||
import Toolbar from "./Toolbar.svelte";
|
||||
import Breadcrumbs from "./Breadcrumbs.svelte";
|
||||
import DetailsWindow from "./DetailsWindow.svelte";
|
||||
import FilePreview from "./viewers/FilePreview.svelte";
|
||||
import FSUploadWidget from "./upload_widget/FSUploadWidget.svelte";
|
||||
import { fs_download, type FSPath } from "./FilesystemAPI";
|
||||
import Menu from "./Menu.svelte";
|
||||
import { fs_download, type FSPath } from "lib/FilesystemAPI";
|
||||
import { FSNavigator } from "./FSNavigator"
|
||||
import { writable } from "svelte/store";
|
||||
import { css_from_path } from "filesystem/edit_window/Branding";
|
||||
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";
|
||||
import { current_page_store } from "wrap/RouterStore";
|
||||
|
||||
let file_viewer: HTMLDivElement
|
||||
let file_preview: FilePreview
|
||||
let toolbar: Toolbar
|
||||
let upload_widget: FSUploadWidget
|
||||
@@ -22,25 +19,37 @@ let details_visible = false
|
||||
let edit_window: EditWindow
|
||||
let edit_visible = false
|
||||
|
||||
const loading = writable(true)
|
||||
const nav = new FSNavigator(true)
|
||||
|
||||
onMount(() => {
|
||||
nav.loading = loading
|
||||
if ((window as any).intial_node !== undefined) {
|
||||
console.debug("Loading initial node")
|
||||
nav.open_node((window as any).initial_node as FSPath, false)
|
||||
} else {
|
||||
console.debug("No initial node, fetching path", window.location.pathname)
|
||||
nav.navigate(decodeURI(window.location.pathname).replace(/^\/d/, ""), false)
|
||||
}
|
||||
|
||||
const page_sub = current_page_store.subscribe(() => {
|
||||
console.debug("Caught page transition to", window.location.pathname)
|
||||
nav.navigate(decodeURI(window.location.pathname).replace(/^\/d/, ""), false)
|
||||
})
|
||||
|
||||
// Subscribe to navigation updates. This function returns a deconstructor
|
||||
// which we can conveniently return from our mount function as well
|
||||
return nav.subscribe(nav => {
|
||||
const nav_sub = nav.subscribe(nav => {
|
||||
if (!nav.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
// Custom CSS rules for the whole viewer
|
||||
document.documentElement.style = css_from_path(nav.path)
|
||||
|
||||
loading.set(false)
|
||||
})
|
||||
return () => {
|
||||
page_sub()
|
||||
nav_sub()
|
||||
document.documentElement.style = ""
|
||||
}
|
||||
})
|
||||
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
@@ -127,23 +136,8 @@ const keydown = (e: KeyboardEvent) => {
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
|
||||
<div bind:this={file_viewer} class="file_viewer">
|
||||
<div class="headerbar">
|
||||
<Menu/>
|
||||
<div class="filesystem">
|
||||
<Breadcrumbs nav={nav}/>
|
||||
</div>
|
||||
|
||||
<div class="viewer_area">
|
||||
<Toolbar
|
||||
bind:this={toolbar}
|
||||
nav={nav}
|
||||
file_viewer={file_viewer}
|
||||
file_preview={file_preview}
|
||||
bind:details_visible={details_visible}
|
||||
edit_window={edit_window}
|
||||
bind:edit_visible={edit_visible}
|
||||
on:download={() => fs_download(nav.base)}
|
||||
/>
|
||||
|
||||
<div class="file_preview">
|
||||
<FilePreview
|
||||
@@ -156,6 +150,15 @@ const keydown = (e: KeyboardEvent) => {
|
||||
on:details={() => details_visible = !details_visible}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Toolbar
|
||||
bind:this={toolbar}
|
||||
nav={nav}
|
||||
bind:details_visible={details_visible}
|
||||
edit_window={edit_window}
|
||||
bind:edit_visible={edit_visible}
|
||||
on:download={() => fs_download(nav.base)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DetailsWindow nav={nav} bind:visible={details_visible} />
|
||||
@@ -168,9 +171,6 @@ const keydown = (e: KeyboardEvent) => {
|
||||
|
||||
<AffiliatePrompt/>
|
||||
|
||||
<LoadingIndicator loading={$loading}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(*) {
|
||||
transition: background-color 0.2s,
|
||||
@@ -183,56 +183,15 @@ const keydown = (e: KeyboardEvent) => {
|
||||
}
|
||||
|
||||
/* Viewer container */
|
||||
.file_viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
.filesystem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
/* Force some variable usage that is normally out of scope */
|
||||
color: var(--body_text_color);
|
||||
|
||||
background-image: var(--background_image, var(--background_pattern));
|
||||
background-color: var(--background_pattern_color);
|
||||
background-size: var(--background_image_size, initial);
|
||||
background-position: var(--background_image_position, initial);
|
||||
background-repeat: var(--background_image_repeat, repeat);
|
||||
}
|
||||
|
||||
/* Headerbar (row 1) */
|
||||
.headerbar {
|
||||
flex: 0 0 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
box-shadow: none;
|
||||
background-color: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* File preview area (row 2) */
|
||||
.viewer_area {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* This max-width needs to be synced with the .toolbar max-width in
|
||||
Toolbar.svelte and the .label max-width in FileStats.svelte */
|
||||
@media (max-width: 1000px) {
|
||||
.viewer_area {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file_preview {
|
||||
flex: 1 1 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--separator);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
import { fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, node_is_shared, type FSNode, type FSPermissions } from "./FilesystemAPI";
|
||||
import { fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, node_is_shared, type FSNode, type FSPermissions } from "lib/FilesystemAPI";
|
||||
import { copy_text } from "util/Util.svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import Dialog from "layout/Dialog.svelte";
|
||||
|
@@ -4,8 +4,7 @@ import { copy_text } from "util/Util.svelte";
|
||||
import FileStats from "./FileStats.svelte";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
import EditWindow from "./edit_window/EditWindow.svelte";
|
||||
import FilePreview from "./viewers/FilePreview.svelte";
|
||||
import { fs_share_url } from "./FilesystemAPI";
|
||||
import { fs_share_url } from "lib/FilesystemAPI";
|
||||
import ShareDialog from "./ShareDialog.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
@@ -14,8 +13,6 @@ export let nav: FSNavigator
|
||||
export let details_visible = false
|
||||
export let edit_window: EditWindow
|
||||
export let edit_visible = false
|
||||
export let file_viewer: HTMLDivElement
|
||||
export let file_preview: FilePreview
|
||||
let share_dialog: ShareDialog
|
||||
|
||||
$: share_url = fs_share_url($nav.path)
|
||||
@@ -30,46 +27,11 @@ export const copy_link = () => {
|
||||
link_copied = true
|
||||
setTimeout(() => {link_copied = false}, 60000)
|
||||
}
|
||||
|
||||
let fullscreen = false
|
||||
export const toggle_fullscreen = () => {
|
||||
if (document.fullscreenElement !== null) {
|
||||
try {
|
||||
document.exitFullscreen()
|
||||
} catch (err) {
|
||||
console.debug("Failed to exit fullscreen", err)
|
||||
}
|
||||
fullscreen = false
|
||||
} else {
|
||||
if (!file_preview.toggle_fullscreen()) {
|
||||
file_viewer.requestFullscreen()
|
||||
}
|
||||
fullscreen = true
|
||||
}
|
||||
}
|
||||
|
||||
let expanded = true
|
||||
let expand = (e: Event) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
expanded = !expanded
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="toolbar" class:expanded>
|
||||
<div class="stats_container" on:click={expand} on:keypress={expand} role="button" tabindex="0">
|
||||
<button class="button_expand hidden_vertical" on:click={expand}>
|
||||
{#if expanded}
|
||||
<i class="icon">expand_more</i>
|
||||
{:else}
|
||||
<i class="icon">expand_less</i>
|
||||
{/if}
|
||||
</button>
|
||||
<FileStats nav={nav}/>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
<div class="toolbar">
|
||||
<div class="grid">
|
||||
<FileStats nav={nav}/>
|
||||
|
||||
<div class="button_row">
|
||||
<button on:click={() => {nav.open_sibling(-1)}}>
|
||||
@@ -83,8 +45,6 @@ let expand = (e: Event) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="separator hidden_horizontal"></div>
|
||||
|
||||
<button on:click={() => dispatch("download")}>
|
||||
<i class="icon">save</i>
|
||||
<span>Download</span>
|
||||
@@ -105,21 +65,6 @@ let expand = (e: Event) => {
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="toolbar_button"
|
||||
on:click={toggle_fullscreen}
|
||||
class:button_highlight={fullscreen}
|
||||
title="Open page in full screen mode">
|
||||
{#if fullscreen}
|
||||
<i class="icon">fullscreen_exit</i>
|
||||
{:else}
|
||||
<i class="icon">fullscreen</i>
|
||||
{/if}
|
||||
<span>Fullscreen</span>
|
||||
</button>
|
||||
|
||||
<div class="separator hidden_horizontal"></div>
|
||||
|
||||
<button on:click={() => details_visible = !details_visible} class:button_highlight={details_visible}>
|
||||
<i class="icon">help</i>
|
||||
<span>Deta<u>i</u>ls</span>
|
||||
@@ -140,22 +85,17 @@ let expand = (e: Event) => {
|
||||
.toolbar {
|
||||
flex: 0 0 auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
overflow-y: hidden;
|
||||
transition: max-height 0.3s;
|
||||
background-color: var(--shaded_background);
|
||||
border-top: 1px solid var(--separator);
|
||||
|
||||
background: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(7.5em, 1fr));
|
||||
}
|
||||
.separator {
|
||||
height: 1px;
|
||||
margin: 2px 0;
|
||||
width: 100%;
|
||||
background-color: var(--separator);
|
||||
}
|
||||
|
||||
.button_row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -164,46 +104,4 @@ let expand = (e: Event) => {
|
||||
flex: 1 1 auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stats_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.button_expand {
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.hidden_vertical {
|
||||
display: none;
|
||||
}
|
||||
.hidden_horizontal {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* This max-width needs to be synced with the .viewer_area max-width in
|
||||
Toolbar.svelte and the .label max-width in FileStats.svelte */
|
||||
@media (max-width: 1000px) {
|
||||
.toolbar {
|
||||
overflow-y: hidden;
|
||||
max-height: 2.1em;
|
||||
}
|
||||
.toolbar.expanded {
|
||||
overflow-y: scroll;
|
||||
max-height: 25vh;
|
||||
}
|
||||
.stats_container {
|
||||
flex-direction: row;
|
||||
}
|
||||
.separator {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hidden_vertical {
|
||||
display: block;
|
||||
}
|
||||
.hidden_horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from "layout/Button.svelte";
|
||||
import type { FSPermissions, NodeOptions } from "filesystem/FilesystemAPI";
|
||||
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI";
|
||||
import PermissionButton from "./PermissionButton.svelte";
|
||||
|
||||
export let options: NodeOptions
|
||||
|
@@ -2,10 +2,38 @@ import parse from "pure-color/parse";
|
||||
import rgb2hsl from "pure-color/convert/rgb2hsl";
|
||||
import hsl2rgb from "pure-color/convert/hsl2rgb";
|
||||
import rgb2hex from "pure-color/convert/rgb2hex";
|
||||
import type { FSNode, FSNodeProperties } from "lib/FilesystemAPI";
|
||||
|
||||
type Style = {
|
||||
input_background: string,
|
||||
input_hover_background: string,
|
||||
input_text: string,
|
||||
highlight_color: string,
|
||||
highlight_background: string,
|
||||
highlight_text_color: string,
|
||||
link_color: string,
|
||||
danger_color: string,
|
||||
danger_text_color: string,
|
||||
background_color: string,
|
||||
background: string,
|
||||
background_text_color: string,
|
||||
background_pattern_color: string,
|
||||
body_color: string,
|
||||
body_background: string,
|
||||
body_text_color: string,
|
||||
shaded_background: string,
|
||||
separator: string,
|
||||
shadow_color: string,
|
||||
card_color: string,
|
||||
background_image: string,
|
||||
background_image_size: string,
|
||||
background_image_position: string,
|
||||
background_image_repeat: string,
|
||||
}
|
||||
|
||||
// Generate a branding style from a file's properties map
|
||||
export const branding_from_path = path => {
|
||||
let style = {}
|
||||
export const branding_from_path = (path: Array<FSNode>) => {
|
||||
let style = <Style>{}
|
||||
for (let node of path) {
|
||||
add_styles(style, node.properties)
|
||||
}
|
||||
@@ -15,17 +43,17 @@ export const branding_from_path = path => {
|
||||
|
||||
// The last style which was generated is cached, when we don't have a complete
|
||||
// path to generate the style with we will use the cached style as a basis
|
||||
let last_generated_style = {}
|
||||
export const branding_from_node = node => {
|
||||
let last_generated_style = <Style>{}
|
||||
export const branding_from_node = (node: FSNode) => {
|
||||
add_styles(last_generated_style, node.properties)
|
||||
return gen_css(last_generated_style)
|
||||
}
|
||||
|
||||
export const css_from_path = path => {
|
||||
export const css_from_path = (path: Array<FSNode>) => {
|
||||
return gen_css(branding_from_path(path))
|
||||
}
|
||||
|
||||
const gen_css = style => {
|
||||
const gen_css = (style: Style) => {
|
||||
return Object.entries(style).map(([key, value]) => `--${key}:${value}`).join(';');
|
||||
}
|
||||
|
||||
@@ -33,7 +61,7 @@ const gen_css = style => {
|
||||
// existing style which is passed as the first argument. When navigating to a
|
||||
// path this function is executed on every member of the path so all the styles
|
||||
// get combined
|
||||
const add_styles = (style, properties) => {
|
||||
const add_styles = (style: Style, properties: FSNodeProperties) => {
|
||||
if (!properties || !properties.branding_enabled || properties.branding_enabled !== "true") {
|
||||
return
|
||||
}
|
||||
@@ -83,7 +111,7 @@ const add_styles = (style, properties) => {
|
||||
}
|
||||
}
|
||||
|
||||
const add_contrast = (color, amt) => {
|
||||
const add_contrast = (color: string, amt: number) => {
|
||||
let hsl = rgb2hsl(parse(color)) // Convert hex to hsl
|
||||
// If the lightness is less than 40 it is considered a dark colour. This
|
||||
// threshold is 40 instead of 50 because overall dark text is more legible
|
||||
@@ -96,20 +124,20 @@ const add_contrast = (color, amt) => {
|
||||
}
|
||||
|
||||
// Darken and desaturate. Only used for shadows
|
||||
const darken = (color, percent) => {
|
||||
const darken = (color: string, percent: number) => {
|
||||
let hsl = rgb2hsl(parse(color)) // Convert hex to hsl
|
||||
hsl[1] = hsl[1] * percent
|
||||
hsl[2] = hsl[2] * percent
|
||||
return rgb2hex(hsl2rgb(hsl)) // Convert back to hex
|
||||
}
|
||||
|
||||
const set_alpha = (color, amt) => {
|
||||
const set_alpha = (color: string, amt: number) => {
|
||||
let rgb = parse(color)
|
||||
rgb.push(amt)
|
||||
return "rgba(" + rgb.join(", ") + ")"
|
||||
}
|
||||
|
||||
const generate_link_color = (link_color, body_color) => {
|
||||
const generate_link_color = (link_color: string, body_color: string) => {
|
||||
let link = rgb2hsl(parse(link_color))
|
||||
let body = rgb2hsl(parse(body_color))
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ThemePresets from "./ThemePresets.svelte";
|
||||
import { fs_update, fs_node_type, type FSNode, type NodeOptions, node_is_shared, type FSPermissions } from "filesystem/FilesystemAPI";
|
||||
import { fs_update, fs_node_type, type FSNode, type NodeOptions, node_is_shared, type FSPermissions } from "lib/FilesystemAPI";
|
||||
import CustomBanner from "filesystem/viewers/CustomBanner.svelte";
|
||||
import HelpButton from "layout/HelpButton.svelte";
|
||||
import FilePicker from "filesystem/filemanager/FilePicker.svelte";
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fs_rename, fs_update, type FSNode, type NodeOptions } from "filesystem/FilesystemAPI";
|
||||
import { fs_rename, fs_update, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
|
||||
import Modal from "util/Modal.svelte";
|
||||
import BrandingOptions from "./BrandingOptions.svelte";
|
||||
import { branding_from_node } from "./Branding";
|
||||
@@ -39,18 +39,9 @@ export const edit = (f: FSNode, oae = false, open_tab = "") => {
|
||||
}
|
||||
|
||||
options.custom_domain_name = file.custom_domain_name
|
||||
|
||||
options.shared = !(file.id === undefined || file.id === "")
|
||||
if (options.shared) {
|
||||
if (file.link_permissions === undefined) {
|
||||
// Default to read-only for public links
|
||||
file.link_permissions = { owner: false, read: true, write: false, delete: false}
|
||||
} else {
|
||||
options.link_permissions = file.link_permissions
|
||||
}
|
||||
options.user_permissions = file.user_permissions
|
||||
options.password_permissions = file.password_permissions
|
||||
}
|
||||
|
||||
branding_enabled = options.branding_enabled === "true"
|
||||
if (branding_enabled) {
|
||||
@@ -122,7 +113,7 @@ const save = async (keep_editing = false) => {
|
||||
<i class="icon">share</i>
|
||||
Sharing
|
||||
</button>
|
||||
{#if options.shared && $nav.permissions.owner}
|
||||
{#if $nav.permissions.owner}
|
||||
<button class:button_highlight={tab === "access"} on:click={() => tab = "access"}>
|
||||
<i class="icon">key</i>
|
||||
Access control
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from "layout/Button.svelte";
|
||||
import { fs_delete_all, type FSNode } from "filesystem/FilesystemAPI";
|
||||
import { fs_delete_all, type FSNode } from "lib/FilesystemAPI";
|
||||
import PathLink from "filesystem/util/PathLink.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ToggleButton from "layout/ToggleButton.svelte";
|
||||
import type { FSPermissions } from "filesystem/FilesystemAPI";
|
||||
import type { FSPermissions } from "lib/FilesystemAPI";
|
||||
|
||||
export let permissions = <FSPermissions>{}
|
||||
</script>
|
||||
|
@@ -1,11 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { domain_url } from "util/Util.svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import type { FSNode, NodeOptions } from "filesystem/FilesystemAPI";
|
||||
import { node_is_shared, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
|
||||
import AccessControl from "./AccessControl.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
export let file: FSNode = {} as FSNode
|
||||
export let options: NodeOptions
|
||||
|
||||
@@ -14,8 +13,8 @@ let preview_area: HTMLDivElement
|
||||
|
||||
$: share_link = window.location.protocol+"//"+window.location.host+"/d/"+file.id
|
||||
$: embed_iframe(file, options)
|
||||
let embed_iframe = (file: FSNode, options: NodeOptions) => {
|
||||
if (!options.shared) {
|
||||
const embed_iframe = (file: FSNode, options: NodeOptions) => {
|
||||
if (!node_is_shared(file)) {
|
||||
example = false
|
||||
embed_html = "File is not shared, can't generate embed code"
|
||||
return
|
||||
@@ -31,7 +30,7 @@ let embed_iframe = (file: FSNode, options: NodeOptions) => {
|
||||
|
||||
let example = false
|
||||
const toggle_example = () => {
|
||||
if (options.shared) {
|
||||
if (node_is_shared(file)) {
|
||||
example = !example
|
||||
if (example) {
|
||||
preview_area.innerHTML = embed_html
|
||||
@@ -41,15 +40,6 @@ const toggle_example = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const update_shared = () => {
|
||||
// If sharing is enabled we automatically save the file so the user can copy
|
||||
// the sharing link. But if the user disables sharing we don't automatically
|
||||
// save so that the user can't accidentally discard a sharing link that's in
|
||||
// use
|
||||
if (options.shared && !file.id) {
|
||||
dispatch("save")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
@@ -64,34 +54,14 @@ const update_shared = () => {
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<input
|
||||
form="edit_form"
|
||||
bind:checked={options.shared}
|
||||
on:change={update_shared}
|
||||
id="shared"
|
||||
type="checkbox"
|
||||
class="form_input"
|
||||
/>
|
||||
<label for="shared">Share this file or directory</label>
|
||||
</div>
|
||||
<div class="link_grid">
|
||||
{#if options.shared}
|
||||
<span>Public link: <a href={share_link}>{share_link}</a></span>
|
||||
<a href={share_link}>{share_link}</a>
|
||||
<CopyButton text={share_link}>Copy</CopyButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
When a file or directory is shared it can be accessed through a
|
||||
unique link. You can get the URL with the 'Copy link' button on
|
||||
the toolbar, or share the link with the 'Share' button. If you
|
||||
share a directory all the files within the directory are also
|
||||
accessible from the link.
|
||||
</p>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<AccessControl options={options}/>
|
||||
|
||||
<fieldset>
|
||||
<legend>Embedding</legend>
|
||||
<p>
|
||||
@@ -108,7 +78,7 @@ const update_shared = () => {
|
||||
<textarea bind:value={embed_html} style="width: 100%; height: 4em;"></textarea>
|
||||
<br/>
|
||||
<CopyButton text={embed_html}>Copy HTML</CopyButton>
|
||||
<button on:click={toggle_example} class:button_highlight={example} disabled={!options.shared}>
|
||||
<button on:click={toggle_example} class:button_highlight={example} disabled={!node_is_shared(file)}>
|
||||
<i class="icon">visibility</i> Show example
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { FSNodeProperties } from "filesystem/FilesystemAPI";
|
||||
import type { FSNodeProperties } from "lib/FilesystemAPI";
|
||||
|
||||
export let properties: FSNodeProperties = {} as FSNodeProperties
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "filesystem/FilesystemAPI"
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI"
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { FileAction } from "./FileManagerLib";
|
||||
|
||||
@@ -36,21 +36,14 @@ export let hide_edit = false
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if $nav.permissions.write && !hide_edit}
|
||||
{#if !hide_edit}
|
||||
<button
|
||||
class="action_button flat"
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Edit, original: e})}
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
|
||||
>
|
||||
<i class="icon">edit</i>
|
||||
<i class="icon">menu</i>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="action_button flat"
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Download, original: e})}
|
||||
>
|
||||
<i class="icon">save</i>
|
||||
</button>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -71,9 +64,9 @@ export let hide_edit = false
|
||||
color: var(--body_text-color);
|
||||
padding: 2px;
|
||||
align-items: center;
|
||||
background: var(--input_background);
|
||||
background: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 1px 8px 0px var(--shadow_color);
|
||||
gap: 6px;
|
||||
}
|
||||
.node:hover:not(.node_selected) {
|
||||
@@ -92,7 +85,6 @@ export let hide_edit = false
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.node_name {
|
||||
flex: 1 1 content;
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { fs_mkdir } from "filesystem/FilesystemAPI";
|
||||
import { fs_mkdir } from "lib/FilesystemAPI";
|
||||
import Button from "layout/Button.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let nav: FSNavigator
|
||||
|
||||
@@ -14,7 +15,7 @@ let create_dir = async () => {
|
||||
form.append("type", "dir")
|
||||
|
||||
try {
|
||||
nav.set_loading(true)
|
||||
loading_start()
|
||||
await fs_mkdir(nav.base.path+"/"+new_dir_name)
|
||||
new_dir_name = "" // Clear input field
|
||||
error_msg = "" // Clear error msg
|
||||
@@ -26,7 +27,7 @@ let create_dir = async () => {
|
||||
error_msg = "Server returned an error: code: '"+err.value+"' message: "+err.message
|
||||
}
|
||||
} finally {
|
||||
nav.set_loading(false)
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "filesystem/FilesystemAPI"
|
||||
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "lib/FilesystemAPI"
|
||||
import { onMount } from "svelte"
|
||||
import CreateDirectory from "./CreateDirectory.svelte"
|
||||
import ListView from "./ListView.svelte"
|
||||
@@ -13,6 +13,7 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
|
||||
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
|
||||
import { FileAction, type FileEvent } from "./FileManagerLib";
|
||||
import FileMenu from "./FileMenu.svelte";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let upload_widget: FsUploadWidget
|
||||
@@ -23,6 +24,7 @@ let uploader: FsUploadWidget
|
||||
let mode = "viewing"
|
||||
let creating_dir = false
|
||||
let show_hidden = false
|
||||
let file_menu: FileMenu
|
||||
|
||||
export const upload = (files: File[]) => {
|
||||
return uploader.upload(files)
|
||||
@@ -84,6 +86,11 @@ const file_event = (e: CustomEvent<FileEvent>) => {
|
||||
e.detail.original.stopPropagation()
|
||||
fs_download(nav.children[index])
|
||||
break
|
||||
case FileAction.Menu:
|
||||
e.detail.original.preventDefault()
|
||||
e.detail.original.stopPropagation()
|
||||
file_menu.open(nav.children[index], e.detail.original.target)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,10 +417,10 @@ onMount(() => {
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<FileMenu bind:this={file_menu} bind:nav bind:edit_window />
|
||||
|
||||
<style>
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
|
@@ -3,4 +3,4 @@ export type FileEvent = {
|
||||
action: FileAction,
|
||||
original: MouseEvent,
|
||||
}
|
||||
export enum FileAction { Click, Context, Edit, Share, Branding, Select, Download }
|
||||
export enum FileAction { Click, Context, Edit, Share, Branding, Select, Download, Menu }
|
||||
|
64
svelte/src/filesystem/filemanager/FileMenu.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import Button from "layout/Button.svelte";
|
||||
import Dialog from "layout/Dialog.svelte";
|
||||
import { bookmark_add, bookmark_del, bookmarks_store } from "lib/Bookmarks";
|
||||
import { fs_download, type FSNode } from "lib/FilesystemAPI";
|
||||
import { tick } from "svelte";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let edit_window: EditWindow
|
||||
let dialog: Dialog
|
||||
let node: FSNode
|
||||
let is_bookmark: boolean = false
|
||||
|
||||
export const open = async (n: FSNode, target: EventTarget) => {
|
||||
node = n
|
||||
|
||||
is_bookmark = false
|
||||
for (const bm of $bookmarks_store) {
|
||||
console.log(bm)
|
||||
if (bm.id === n.id) {
|
||||
is_bookmark = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the view to update, so the dialog gets the proper measurements
|
||||
await tick()
|
||||
dialog.open((target as Element).closest("button").getBoundingClientRect())
|
||||
}
|
||||
|
||||
const bookmark = () => {
|
||||
bookmark_add({
|
||||
id: node.id,
|
||||
icon: "folder_shared",
|
||||
label: node.name,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog bind:this={dialog}>
|
||||
<div class="menu">
|
||||
<Button click={() => {dialog.close(); fs_download(node)}} icon="save" label="Download"/>
|
||||
{#if is_bookmark}
|
||||
<Button click={() => {dialog.close(); bookmark_del(node.id)}} icon="bookmark_remove" label="Remove bookmark"/>
|
||||
{:else}
|
||||
<Button click={() => {dialog.close(); bookmark()}} icon="bookmark_add" label="Add bookmark"/>
|
||||
{/if}
|
||||
{#if $nav.permissions.write}
|
||||
<Button click={() => {dialog.close(); edit_window.edit(node, false, "file")}} icon="edit" label="Edit"/>
|
||||
<Button click={() => {dialog.close(); edit_window.edit(node, false, "share")}} icon="share" label="Share"/>
|
||||
<Button click={() => {dialog.close(); edit_window.edit(node, false, "branding")}} icon="palette" label="Branding"/>
|
||||
{/if}
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 20em;
|
||||
}
|
||||
</style>
|
@@ -4,17 +4,15 @@ import ListView from "./ListView.svelte"
|
||||
import GalleryView from "./GalleryView.svelte"
|
||||
import CompactView from "./CompactView.svelte"
|
||||
import Modal from "util/Modal.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Breadcrumbs from "filesystem/Breadcrumbs.svelte"
|
||||
import { FSNavigator } from "filesystem/FSNavigator";
|
||||
import type { FSNode } from "filesystem/FilesystemAPI";
|
||||
import type { FSNode } from "lib/FilesystemAPI";
|
||||
import { FileAction, type FileEvent } from "./FileManagerLib";
|
||||
|
||||
let nav = new FSNavigator(false)
|
||||
let modal: Modal
|
||||
let dispatch = createEventDispatcher()
|
||||
let directory_view = ""
|
||||
let loading = false
|
||||
let large_icons = false
|
||||
let show_hidden = false
|
||||
export let select_multiple = false
|
||||
@@ -194,8 +192,6 @@ onMount(() => {
|
||||
on:file={file_event}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { fs_node_icon, fs_node_type, fs_encode_path } from "filesystem/FilesystemAPI";
|
||||
import { fs_node_icon, fs_node_type, fs_encode_path } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { FileAction } from "./FileManagerLib";
|
||||
let dispatch = createEventDispatcher()
|
||||
@@ -46,15 +46,15 @@ export let large_icons = false
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: var(--input_background);
|
||||
background: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 4px;
|
||||
color: var(--input_text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: background 0.2s;
|
||||
text-decoration: none;
|
||||
padding: 3px;
|
||||
box-shadow: 1px 1px 0px 0px var(--shadow_color);
|
||||
}
|
||||
.file.large_icons {
|
||||
width: 200px;
|
||||
@@ -87,7 +87,7 @@ export let large_icons = false
|
||||
}
|
||||
.node_icon {
|
||||
flex: 1 1 0;
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "filesystem/FilesystemAPI"
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI"
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
import { FileAction } from "./FileManagerLib";
|
||||
@@ -19,7 +19,7 @@ export let hide_branding = false
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="name">Name</SortButton></td>
|
||||
<td><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</SortButton></td>
|
||||
<td class="hide_small"><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</SortButton></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
@@ -60,14 +60,11 @@ export let hide_branding = false
|
||||
<i class="icon">palette</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if $nav.permissions.write && !hide_edit}
|
||||
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Edit, original: e})}>
|
||||
<i class="icon">edit</i>
|
||||
{#if !hide_edit}
|
||||
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}>
|
||||
<i class="icon">menu</i>
|
||||
</button>
|
||||
{/if}
|
||||
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Download, original: e})}>
|
||||
<i class="icon">save</i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</a>
|
||||
@@ -77,13 +74,11 @@ export let hide_branding = false
|
||||
<style>
|
||||
.directory {
|
||||
display: table;
|
||||
margin: 8px auto 16px auto;
|
||||
background: var(--shaded_background);
|
||||
border-collapse: collapse;
|
||||
border-radius: 8px;
|
||||
|
||||
max-width: 99%;
|
||||
width: 1200px;
|
||||
backdrop-filter: blur(4px);
|
||||
max-width: 1200px;
|
||||
margin: auto; /* center */
|
||||
}
|
||||
.directory > * {
|
||||
display: table-row;
|
||||
@@ -116,7 +111,7 @@ td {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
/* border-radius: 4px; */
|
||||
margin: 2px;
|
||||
}
|
||||
.node_name {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { fs_search, fs_encode_path, fs_thumbnail_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_search, fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let nav: FSNavigator
|
||||
|
||||
@@ -43,9 +44,9 @@ const search = async (limit = 10) => {
|
||||
last_limit = limit
|
||||
|
||||
searching = true
|
||||
nav.set_loading(true)
|
||||
|
||||
try {
|
||||
loading_start()
|
||||
search_results = await fs_search(nav.base.path, search_term, limit)
|
||||
} catch (err) {
|
||||
if (err.value) {
|
||||
@@ -54,6 +55,8 @@ const search = async (limit = 10) => {
|
||||
alert(err)
|
||||
console.error(err)
|
||||
}
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
|
||||
if (search_results.length > 0 && selected_result > search_results.length-1) {
|
||||
@@ -61,7 +64,6 @@ const search = async (limit = 10) => {
|
||||
}
|
||||
|
||||
searching = false
|
||||
nav.set_loading(false)
|
||||
|
||||
// It's possible that the user entered another letter while we were
|
||||
// performing the search reqeust. If this happens we run the search function
|
||||
@@ -218,7 +220,6 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
max-width: 100%;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid var(--separator);
|
||||
}
|
||||
|
||||
.search_form {
|
||||
|
@@ -11,14 +11,11 @@ export type UploadJob = {
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import { tick } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import UploadProgress from "./UploadProgress.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
|
||||
const max_concurrent_uploads = 5
|
||||
|
||||
let file_input_field: HTMLInputElement
|
||||
let file_input_change = (e: Event) => {
|
||||
// Start uploading the files async
|
||||
@@ -91,22 +88,29 @@ let active_uploads = 0
|
||||
let state = "idle"
|
||||
|
||||
const start_upload = async () => {
|
||||
// Count the number of active uploads so we can know how many new uploads we
|
||||
// can start
|
||||
active_uploads = upload_queue.reduce((acc, val) => {
|
||||
if (val.status === "uploading") {
|
||||
acc++
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
|
||||
for (let i = 0; i < upload_queue.length && active_uploads < max_concurrent_uploads; i++) {
|
||||
active_uploads = 0
|
||||
let uploading_size = 0
|
||||
for (let i = 0; i < upload_queue.length; i++) {
|
||||
if (upload_queue[i]) {
|
||||
// If this file is queued, start the upload
|
||||
if (upload_queue[i].status === "queued") {
|
||||
active_uploads++
|
||||
upload_queue[i].component.start()
|
||||
upload_queue[i].status = "uploading"
|
||||
}
|
||||
|
||||
// If this file is already uploading (or just started), count it
|
||||
if (upload_queue[i].status === "uploading") {
|
||||
uploading_size += upload_queue[i].total_size
|
||||
active_uploads++
|
||||
}
|
||||
|
||||
// If the size threshold or the concurrent upload limit is reached
|
||||
// we break the loop. The system tries to keep an upload queue of
|
||||
// 100 MB and a minimum of two concurrent uploads.
|
||||
if ((uploading_size >= 100e6 && active_uploads >= 2) || active_uploads >= 10) {
|
||||
console.debug("Current uploads", active_uploads, "uploads size", uploading_size)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +157,7 @@ const leave_confirmation = (e: BeforeUnloadEvent) => {
|
||||
/>
|
||||
|
||||
{#if visible}
|
||||
<div class="upload_widget" transition:fade={{duration: 200}}>
|
||||
<div class="upload_widget">
|
||||
<div class="header">
|
||||
{#if state === "idle"}
|
||||
Waiting for files
|
||||
@@ -184,7 +188,8 @@ const leave_confirmation = (e: BeforeUnloadEvent) => {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 500px;
|
||||
width: auto;
|
||||
min-width: 400px;
|
||||
max-width: 80%;
|
||||
height: auto;
|
||||
max-height: 50%;
|
||||
|
@@ -9,7 +9,7 @@
|
||||
//
|
||||
// on_error is called when the upload has failed. The parameters are the error
|
||||
|
||||
import { fs_path_url, type GenericResponse } from "filesystem/FilesystemAPI"
|
||||
import { fs_path_url, type GenericResponse } from "lib/FilesystemAPI"
|
||||
|
||||
// code and an error message
|
||||
export const upload_file = (
|
||||
|
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import { upload_file } from "./UploadFunc";
|
||||
import ProgressBar from "util/ProgressBar.svelte";
|
||||
import Button from "layout/Button.svelte"
|
||||
@@ -56,14 +55,14 @@ const cancel = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="prog" transition:fade={{duration: 200}} class:error={job.status === "error"}>
|
||||
<div class="prog" class:error={job.status === "error"}>
|
||||
<div class="bar">
|
||||
{job.file.name}<br/>
|
||||
{#if error_code !== ""}
|
||||
{error_message}<br/>
|
||||
{error_code}<br/>
|
||||
{/if}
|
||||
<ProgressBar total={total} used={loaded}/>
|
||||
<ProgressBar total={total} used={loaded} speed={500}/>
|
||||
</div>
|
||||
<div class="cancel">
|
||||
<Button icon="cancel" click={cancel}/>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { fs_path_url, fs_encode_path, fs_node_icon } from "filesystem/FilesystemAPI"
|
||||
import FileTitle from "layout/FileTitle.svelte";
|
||||
import { fs_path_url, fs_encode_path, fs_node_icon } from "lib/FilesystemAPI"
|
||||
import TextBlock from "layout/TextBlock.svelte"
|
||||
import type { FSNavigator } from 'filesystem/FSNavigator';
|
||||
|
||||
@@ -50,8 +49,6 @@ onMount(() => {
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<FileTitle title={$nav.base.name}/>
|
||||
|
||||
<TextBlock width="1000px">
|
||||
<audio
|
||||
bind:this={player}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import IconBlock from "layout/IconBlock.svelte";
|
||||
import { fs_thumbnail_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_thumbnail_url } from "lib/FilesystemAPI";
|
||||
import TextBlock from "layout/TextBlock.svelte"
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from "svelte";
|
||||
import Spinner from "util/Spinner.svelte";
|
||||
import { fs_node_type } from "filesystem/FilesystemAPI";
|
||||
import { fs_node_type } from "lib/FilesystemAPI";
|
||||
import FileManager from "filesystem/filemanager/FileManager.svelte";
|
||||
import Audio from "./Audio.svelte";
|
||||
import File from "./File.svelte";
|
||||
@@ -74,7 +74,7 @@ export const seek = (delta: number) => {
|
||||
|
||||
{#if viewer_type === ""}
|
||||
<div class="center">
|
||||
<Spinner></Spinner>
|
||||
<Spinner/>
|
||||
</div>
|
||||
{:else if viewer_type === "dir"}
|
||||
<FileManager nav={nav} upload_widget={upload_widget} edit_window={edit_window}>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { swipe_nav } from "lib/SwipeNavigate";
|
||||
import { fs_path_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fs_path_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { tick } from "svelte";
|
||||
import { fs_path_url, type FSNode } from "filesystem/FilesystemAPI";
|
||||
import { fs_path_url, type FSNode } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
|
@@ -19,9 +19,10 @@ import { formatDate } from "util/Formatting"
|
||||
import TorrentItem from "./TorrentItem.svelte"
|
||||
import IconBlock from "layout/IconBlock.svelte";
|
||||
import TextBlock from "layout/TextBlock.svelte"
|
||||
import { fs_node_icon, fs_path_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
@@ -31,7 +32,7 @@ let status = "loading"
|
||||
|
||||
export const update = async () => {
|
||||
try {
|
||||
nav.set_loading(true)
|
||||
loading_start()
|
||||
let resp = await fetch(fs_path_url(nav.base.path)+"?torrent_info")
|
||||
|
||||
if (resp.status >= 400) {
|
||||
@@ -58,7 +59,7 @@ export const update = async () => {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
nav.set_loading(false)
|
||||
loading_finish()
|
||||
status = "finished"
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher, tick } from "svelte";
|
||||
import { video_position } from "lib/VideoPosition";
|
||||
import { fs_path_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
|
@@ -13,8 +13,9 @@ import { formatDataVolume, formatDate } from "util/Formatting"
|
||||
import ZipItem from "filesystem/viewers/ZipItem.svelte";
|
||||
import IconBlock from "layout/IconBlock.svelte";
|
||||
import TextBlock from "layout/TextBlock.svelte"
|
||||
import { fs_node_icon, fs_path_url } from "filesystem/FilesystemAPI";
|
||||
import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
@@ -36,8 +37,8 @@ export const update = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
loading_start()
|
||||
status = "loading"
|
||||
nav.set_loading(true)
|
||||
let resp = await fetch(fs_path_url(nav.base.path)+"?zip_info")
|
||||
|
||||
if (resp.status >= 400) {
|
||||
@@ -64,7 +65,7 @@ export const update = async () => {
|
||||
console.error(err)
|
||||
status = "parse_failed"
|
||||
} finally {
|
||||
nav.set_loading(false)
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +0,0 @@
|
||||
import App from './home_page/HomePage.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("page_body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
@@ -15,7 +15,7 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="vertical_scroll">
|
||||
<div class="horizontal_scroll">
|
||||
<div class="grid">
|
||||
<div></div>
|
||||
<div class="top_row free_feat">
|
||||
@@ -271,20 +271,21 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.vertical_scroll {
|
||||
.horizontal_scroll {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: inline-grid;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-template-columns: 9em 1fr 1fr 1fr;
|
||||
min-width: 40em;
|
||||
min-width: 50em;
|
||||
max-width: 70em;
|
||||
gap: 5px;
|
||||
margin: 10px;
|
||||
margin: auto;
|
||||
}
|
||||
.grid > div {
|
||||
justify-content: center;
|
||||
|
@@ -5,6 +5,7 @@ import { drop_target } from "lib/DropTarget";
|
||||
import AddressReputation from "./AddressReputation.svelte";
|
||||
import FeatureTable from "./FeatureTable.svelte";
|
||||
import GetStarted from "./GetStarted.svelte";
|
||||
import Pricing from "./Pricing.svelte";
|
||||
|
||||
let upload_widget
|
||||
</script>
|
||||
@@ -50,6 +51,7 @@ let upload_widget
|
||||
Bullet lists
|
||||
</li>
|
||||
</ul>
|
||||
<Pricing/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -141,26 +143,18 @@ let upload_widget
|
||||
<FeatureTable/>
|
||||
</div>
|
||||
|
||||
<Footer nobg/>
|
||||
|
||||
<svelte:head>
|
||||
<style>
|
||||
:global(.page_body) {
|
||||
background-image: url("/res/img/inflating_star.webp");
|
||||
body {
|
||||
background-image: url("/res/img/catspaw.webp");
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
.page_content {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.page_content {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</svelte:head>
|
||||
<style>
|
||||
header {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
@@ -54,6 +54,7 @@ let click_int = (e: MouseEvent) => {
|
||||
</button>
|
||||
{:else}
|
||||
<a
|
||||
on:click={click_int}
|
||||
href="{link_href}"
|
||||
target={link_target}
|
||||
class="button"
|
||||
|
@@ -22,6 +22,10 @@ export const open = (button_rect: DOMRect) => {
|
||||
dialog.style.top = Math.round(Math.min(min_top, max_top)) + "px"
|
||||
}
|
||||
|
||||
export const close = () => {
|
||||
dialog.close()
|
||||
}
|
||||
|
||||
// Attach a click handler so that the dialog is closed when the user clicks
|
||||
// outside the dialog. The way this check works is that there is usually an
|
||||
// element inside the dialog with all the content. When the click target is
|
||||
|
@@ -39,7 +39,6 @@ export let width = "750px"
|
||||
overflow-wrap: anywhere;
|
||||
background-color: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -5,7 +5,7 @@ export let asc = true
|
||||
export let sort_func: (field: string) => void
|
||||
</script>
|
||||
|
||||
<button class:button_highlight={active_field === field} on:click={() => sort_func(field)}>
|
||||
<button on:click={() => sort_func(field)}>
|
||||
{#if active_field === field}
|
||||
{#if asc}↓{:else}↑{/if}
|
||||
{/if}
|
||||
@@ -18,6 +18,6 @@ button {
|
||||
margin: 0;
|
||||
line-height: 1em;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-align: initial;
|
||||
}
|
||||
</style>
|
||||
|
@@ -11,13 +11,12 @@ export let center = false
|
||||
.block {
|
||||
display: block;
|
||||
text-align: initial;
|
||||
max-width: 99%;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
margin: 8px auto;
|
||||
margin: auto;
|
||||
|
||||
background-color: var(--shaded_background);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.center {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import PickAmount from "./PickAmount.svelte";
|
||||
import PickCountry from "./PickCountry.svelte";
|
||||
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
||||
@@ -9,7 +8,6 @@ import { get_misc_vat_rate, get_user } from "lib/PixeldrainAPI";
|
||||
import { countries } from "country-data-list";
|
||||
import PickProvider from "./PickProvider.svelte";
|
||||
|
||||
let loading = false
|
||||
let state: CheckoutState = {country: null, provider: null, amount: 0, vat: 0, name: ""}
|
||||
|
||||
onMount(async () => {
|
||||
@@ -42,8 +40,6 @@ onMount(async () => {
|
||||
</script>
|
||||
|
||||
<div class="highlight_border">
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
{#if state.country !== null}
|
||||
<div class="navbar">
|
||||
{#if state.country !== null}
|
||||
@@ -76,13 +72,13 @@ onMount(async () => {
|
||||
{/if}
|
||||
|
||||
{#if state.country === null}
|
||||
<PickCountry bind:state bind:loading={loading}/>
|
||||
<PickCountry bind:state/>
|
||||
{:else if state.provider === null}
|
||||
<PickProvider bind:state/>
|
||||
{:else if state.provider.need_name === true && state.name === ""}
|
||||
<PickName bind:state/>
|
||||
{:else}
|
||||
<PickAmount bind:state bind:loading/>
|
||||
<PickAmount bind:state/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { checkout, type CheckoutState } from "./CheckoutLib";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let state: CheckoutState
|
||||
export let loading: boolean
|
||||
|
||||
const amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
|
||||
let amount = 20
|
||||
@@ -11,10 +11,13 @@ let amount = 20
|
||||
const submit = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
loading = true
|
||||
state.amount = amount
|
||||
loading_start()
|
||||
try {
|
||||
await checkout(state)
|
||||
loading = false
|
||||
}finally {
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -2,13 +2,12 @@
|
||||
import { countries } from "country-data-list";
|
||||
import { get_misc_vat_rate, put_user } from "lib/PixeldrainAPI";
|
||||
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let state: CheckoutState
|
||||
export let loading: boolean
|
||||
let country_input = ""
|
||||
|
||||
const set_country = async (e?: Event) => {
|
||||
loading = true
|
||||
if (e !== undefined) {
|
||||
e.preventDefault()
|
||||
}
|
||||
@@ -24,6 +23,7 @@ const set_country = async (e?: Event) => {
|
||||
// with an invalid combination
|
||||
state.provider = null
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
// Save the user's country for later use
|
||||
await put_user({checkout_country: c.alpha3})
|
||||
@@ -34,7 +34,7 @@ const set_country = async (e?: Event) => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
96
svelte/src/lib/Bookmarks.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { writable } from "svelte/store"
|
||||
import { fs_check_response, fs_path_url } from "./FilesystemAPI"
|
||||
import { loading_finish, loading_start } from "lib/Loading"
|
||||
|
||||
const bookmarks_file = "/me/.fnx/bookmarks.json"
|
||||
|
||||
export type Bookmark = {
|
||||
id: string,
|
||||
icon: string,
|
||||
label: string,
|
||||
}
|
||||
|
||||
export let bookmarks_store = writable<Bookmark[]>(
|
||||
[],
|
||||
(set: (value: Bookmark[]) => void) => {
|
||||
bookmarks_get().then((bm: Bookmark[]) => {
|
||||
set(bm)
|
||||
}).catch((err: any) => {
|
||||
alert("Could not fetch bookmarks:\n" + JSON.stringify(err))
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const bookmarks_get = async (): Promise<Bookmark[]> => {
|
||||
let bookmarks: Bookmark[] = []
|
||||
try {
|
||||
bookmarks = await fs_check_response(
|
||||
await fetch(fs_path_url(bookmarks_file), { cache: "no-store" })
|
||||
)
|
||||
} catch (err) {
|
||||
// If the bookmarks were not found when we return an empty bookmarks
|
||||
// list
|
||||
if (err.value !== "path_not_found") {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
console.debug("Fetched", bookmarks.length, "bookmarks:", bookmarks)
|
||||
bookmarks_store.set(bookmarks)
|
||||
return bookmarks
|
||||
}
|
||||
|
||||
export const bookmarks_save = async (bookmarks: Bookmark[]) => {
|
||||
await fs_check_response(
|
||||
await fetch(
|
||||
fs_path_url(bookmarks_file) + "?make_parents=true",
|
||||
{ method: "PUT", body: JSON.stringify(bookmarks) },
|
||||
)
|
||||
)
|
||||
console.debug("Saved", bookmarks.length, "bookmarks:", bookmarks)
|
||||
bookmarks_store.set(bookmarks)
|
||||
}
|
||||
|
||||
export const bookmark_add = async (bm: Bookmark): Promise<Bookmark[]> => {
|
||||
let bookmarks: Bookmark[] = []
|
||||
try {
|
||||
loading_start()
|
||||
|
||||
// Get bookmarks
|
||||
bookmarks = await bookmarks_get()
|
||||
|
||||
// Add new bookmark
|
||||
bookmarks.push(bm)
|
||||
|
||||
// Save new bookmarks
|
||||
await bookmarks_save(bookmarks)
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
return bookmarks
|
||||
}
|
||||
|
||||
export const bookmark_del = async (id: string): Promise<Bookmark[]> => {
|
||||
let bookmarks: Bookmark[] = []
|
||||
try {
|
||||
loading_start()
|
||||
|
||||
// Get bookmarks
|
||||
bookmarks = await bookmarks_get()
|
||||
|
||||
// Delete bookmark
|
||||
for (let i = 0; i < bookmarks.length; i++) {
|
||||
if (bookmarks[i].id === id) {
|
||||
console.debug("Deleting bookmark at index", i, bookmarks[i])
|
||||
bookmarks.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Save new bookmarks
|
||||
await bookmarks_save(bookmarks)
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
return bookmarks
|
||||
}
|
@@ -46,7 +46,11 @@ export type FSNode = {
|
||||
}
|
||||
|
||||
export const node_is_shared = (node: FSNode): boolean => {
|
||||
if (node.link_permissions !== undefined && node.link_permissions.read) {
|
||||
if (
|
||||
(node.link_permissions !== undefined && node.link_permissions.read === true) ||
|
||||
(node.user_permissions !== undefined && Object.keys(node.user_permissions).length > 0) ||
|
||||
(node.password_permissions !== undefined && Object.keys(node.password_permissions).length > 0)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
22
svelte/src/lib/HighlightCurrentPage.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { current_page_store, type Tab } from "wrap/RouterStore"
|
||||
|
||||
export const highlight_current_page = (node: HTMLAnchorElement) => {
|
||||
const set_highlight = () => {
|
||||
if (window.location.pathname === URL.parse(node.href).pathname) {
|
||||
node.classList.add("button_highlight")
|
||||
} else {
|
||||
node.classList.remove("button_highlight")
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the status with the current page path
|
||||
set_highlight()
|
||||
|
||||
// Set up a listener with the page router to catch navigation events
|
||||
const unsub = current_page_store.subscribe((page: Tab) => { set_highlight() })
|
||||
return {
|
||||
destroy() {
|
||||
unsub()
|
||||
}
|
||||
}
|
||||
}
|
20
svelte/src/lib/Loading.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
export const loading_store = writable(0)
|
||||
|
||||
export const loading_start = () => {
|
||||
loading_store.set(get(loading_store) + 1)
|
||||
}
|
||||
|
||||
export const loading_finish = () => {
|
||||
loading_store.set(get(loading_store) - 1)
|
||||
}
|
||||
|
||||
export const loading_run = async <T>(fn: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
loading_start()
|
||||
return await fn()
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
}
|
@@ -106,14 +106,18 @@ export type UserSession = {
|
||||
valid_domains: string[],
|
||||
}
|
||||
|
||||
export const get_user = async () => {
|
||||
let cached_user: User = null
|
||||
export const get_user = async (): Promise<User> => {
|
||||
if ((window as any).user !== undefined) {
|
||||
return (window as any).user as User
|
||||
} else if (cached_user !== null) {
|
||||
return cached_user
|
||||
}
|
||||
|
||||
console.warn("user property is not defined on window")
|
||||
|
||||
return await check_response(await fetch(get_endpoint() + "/user")) as User
|
||||
cached_user = await check_response(await fetch(get_endpoint() + "/user")) as User
|
||||
return cached_user
|
||||
}
|
||||
|
||||
export const put_user = async (data: Object) => {
|
||||
@@ -128,7 +132,6 @@ export const put_user = async (data: Object) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type VATRate = {
|
||||
name: string,
|
||||
vat: number,
|
||||
|
@@ -1,8 +0,0 @@
|
||||
import App from './login/Router.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("page_body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
@@ -27,14 +27,14 @@ onMount(async () => {
|
||||
params.get("link_login_id") === null &&
|
||||
user.username !== ""
|
||||
) {
|
||||
console.debug("User is already logged in, redirecting to user dashboard")
|
||||
console.debug("User is already logged in, redirecting to user dashboard", user)
|
||||
|
||||
let login_redirect = params.get("redirect")
|
||||
if (typeof login_redirect === "string" && login_redirect.startsWith("/")) {
|
||||
console.debug("redirecting user to requested path", login_redirect)
|
||||
window.location.href = window.location.protocol+"//"+window.location.host+login_redirect
|
||||
window.location.pathname = login_redirect
|
||||
} else {
|
||||
window.location.href = "/user"
|
||||
window.location.pathname = "/user"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
145
svelte/src/pages/Appearance.svelte
Normal file
@@ -0,0 +1,145 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const get_cookie = (cname: string) => {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i <ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
let style = get_cookie("style")
|
||||
let hue = +get_cookie("hue")
|
||||
|
||||
// Style selector
|
||||
onMount(() => {
|
||||
document.getElementsByName("style").forEach(function(elem) {
|
||||
elem.addEventListener("change", e => {
|
||||
style = (e.target as HTMLInputElement).id.substring(6)
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (10 * 365 * 24 * 60 * 60 * 1000));
|
||||
document.cookie = "style="+style+"; expires=" + date.toUTCString() + "; path=/"
|
||||
|
||||
reload_sheet()
|
||||
})
|
||||
});
|
||||
document.getElementsByName("hue").forEach(function(elem) {
|
||||
elem.addEventListener("change", e => {
|
||||
hue = +(e.target as HTMLInputElement).id.substring(4)
|
||||
if (hue === -1) {
|
||||
document.cookie = "hue=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (10 * 365 * 24 * 60 * 60 * 1000));
|
||||
document.cookie = "hue="+hue+"; expires=" + date.toUTCString() + "; path=/"
|
||||
|
||||
reload_sheet()
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
let stylesheet_2: HTMLLinkElement
|
||||
const reload_sheet = () => {
|
||||
let stylesheet1 = document.getElementById("stylesheet_theme") as HTMLLinkElement
|
||||
|
||||
// First load the sheet in the secondary tag, wait for it to load,
|
||||
// and replace the original sheet when it has finished loading
|
||||
stylesheet_2.href= "/theme.css?style="+style+"&hue="+hue
|
||||
stylesheet_2.onload = e => {
|
||||
stylesheet1.href= "/theme.css?style="+style+"&hue="+hue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link bind:this={stylesheet_2} id="stylesheet_theme_2" rel="stylesheet" type="text/css" href="/theme.css"/>
|
||||
</svelte:head>
|
||||
|
||||
<div id="page_content" class="page_content">
|
||||
<section>
|
||||
<p>
|
||||
You can change how pixeldrain looks! Your theme choice will
|
||||
be saved in a cookie.
|
||||
</p>
|
||||
<h2>Theme</h2>
|
||||
<input type="radio" id="style_nord" name="style"><label for="style_nord">Nord</label>
|
||||
(Inspired by <a href="https://www.nordtheme.com/" target="_blank">Nord</a>)
|
||||
<br/>
|
||||
Dynamic theme, changes based on operating system settings. Here you can choose a specific variant:
|
||||
<br/>
|
||||
<input type="radio" id="style_nord_dark" name="style"><label for="style_nord_dark">Nord dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_nord_light" name="style"><label for="style_nord_light">Nord light</label>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized" name="style"><label for="style_solarized">Solarized</label>
|
||||
(Inspired by <a href="https://ethanschoonover.com/solarized/" target="_blank">Solarized</a>)
|
||||
<br/>
|
||||
Dynamic theme, changes based on operating system settings. Here you can choose a specific variant:
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized_dark" name="style"><label for="style_solarized_dark">Solarized dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_solarized_light" name="style"><label for="style_solarized_light">Solarized light</label>
|
||||
<br/>
|
||||
<!-- <br/> -->
|
||||
<!-- <input type="radio" id="style_adwaita" name="style"><label for="style_adwaita">Adwaita</label><br/> -->
|
||||
<br/>
|
||||
<input type="radio" id="style_purple_drain" name="style"><label for="style_purple_drain">Purple drain</label>
|
||||
<br/>
|
||||
Classic 2022 style, with purple gradients
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_classic" name="style"><label for="style_classic">Pixeldrain classic (gray)</label>
|
||||
<br/>
|
||||
Classic pre-2020 pixeldrain style, dark gray
|
||||
<br/>
|
||||
<br/>
|
||||
Other (experimental) themes
|
||||
<br/>
|
||||
<input type="radio" id="style_maroon" name="style"><label for="style_maroon">Maroon Style</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_hacker" name="style"><label for="style_hacker">Hacker Style</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_canta" name="style"><label for="style_canta">Canta Style</label>
|
||||
(Inspired by <a href="https://github.com/vinceliuice/Canta-theme" target="_blank">Canta GTK</a>)
|
||||
<br/>
|
||||
<input type="radio" id="style_skeuos" name="style"><label for="style_skeuos">Skeuos Style</label>
|
||||
(Inspired by <a href="https://www.gnome-look.org/p/1441725/" target="_blank">Skeuos GTK</a>)
|
||||
<br/>
|
||||
<input type="radio" id="style_sweet" name="style"><label for="style_sweet">Sweet</label>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita" name="style"><label for="style_adwaita">Adwaita (dynamic)</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita_dark" name="style"><label for="style_adwaita_dark">Adwaita dark</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_adwaita_light" name="style"><label for="style_adwaita_light">Adwaita light</label>
|
||||
<br/><br/>
|
||||
<input type="radio" id="style_pixeldrain98" name="style"><label for="style_pixeldrain98">Pixeldrain 98</label>
|
||||
<br/>
|
||||
<input type="radio" id="style_pixeldrain98_dark" name="style"><label for="style_pixeldrain98_dark">Pixeldrain 98 (dark)</label>
|
||||
|
||||
<h2>Hue</h2>
|
||||
<p>
|
||||
Many themes support custom hues. The hue does not change the
|
||||
contrast of the theme, only the color itself.
|
||||
</p>
|
||||
<input type="radio" id="hue_-1" name="hue"><label for="hue_-1">Default</label><br/>
|
||||
<input type="radio" id="hue_354" name="hue"><label for="hue_354">Red</label><br/>
|
||||
<input type="radio" id="hue_14" name="hue"><label for="hue_14">Orange</label><br/>
|
||||
<input type="radio" id="hue_40" name="hue"><label for="hue_40">Yellow</label><br/>
|
||||
<input type="radio" id="hue_92" name="hue"><label for="hue_92">Green</label><br/>
|
||||
<input type="radio" id="hue_311" name="hue"><label for="hue_311">Purple</label><br/>
|
||||
</section>
|
||||
</div>
|
@@ -1,8 +0,0 @@
|
||||
import App from './speedtest/Index.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("page_body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
@@ -1,13 +1,8 @@
|
||||
<script>
|
||||
import Footer from "layout/Footer.svelte";
|
||||
import Speedtest from "./Speedtest.svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<h1>Pixeldrain Speedtest</h1>
|
||||
</header>
|
||||
|
||||
<div class="page_content">
|
||||
<Speedtest/>
|
||||
|
||||
@@ -106,11 +101,3 @@ import Speedtest from "./Speedtest.svelte";
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
|
||||
<style>
|
||||
.page_content {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
@@ -1,8 +0,0 @@
|
||||
import App from './user_home/Router.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("page_body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
@@ -1,13 +1,12 @@
|
||||
<script>
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
|
||||
let loading = false
|
||||
let loaded = false
|
||||
let rows = []
|
||||
|
||||
const load_keys = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/session")
|
||||
if(resp.status >= 400) {
|
||||
@@ -30,13 +29,13 @@ const load_keys = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
loaded = true
|
||||
}
|
||||
};
|
||||
|
||||
const create_key = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
try {
|
||||
let form = new FormData()
|
||||
form.append("app_name", "website keys page")
|
||||
@@ -53,14 +52,15 @@ const create_key = async () => {
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Failed to create new API key! "+err)
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
|
||||
load_keys();
|
||||
}
|
||||
|
||||
const logout = async (key) => {
|
||||
loading = true
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/user/session",
|
||||
@@ -76,14 +76,14 @@ const logout = async (key) => {
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Failed to delete key: "+err)
|
||||
} finally {
|
||||
loading_finish()
|
||||
}
|
||||
|
||||
load_keys();
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
{#if !loaded}
|
||||
<div class="highlight_yellow">
|
||||
|
@@ -1,9 +1,7 @@
|
||||
<script>
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
|
||||
let loading = false
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
@@ -11,7 +9,7 @@ let month_str = ""
|
||||
let data = []
|
||||
|
||||
const load_activity = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/activity/" + month_str)
|
||||
@@ -29,7 +27,7 @@ const load_activity = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
const last_month = () => {
|
||||
@@ -59,8 +57,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h2>Account activity log</h2>
|
||||
<p>
|
||||
|
@@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Modal from "util/Modal.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import { get_user, put_user } from "lib/PixeldrainAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
// When the always flag is set then the pop-up will also show if the user
|
||||
// already has an affiliate ID set
|
||||
export let always = false
|
||||
let modal: Modal
|
||||
let loading: boolean
|
||||
let referral: string
|
||||
let shown = false
|
||||
|
||||
@@ -50,7 +49,7 @@ export const prompt = async (ref: string) => {
|
||||
onMount(() => prompt(new URLSearchParams(document.location.search).get("ref")))
|
||||
|
||||
const allow = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
try {
|
||||
await put_user({affiliate_user_name: referral})
|
||||
|
||||
@@ -59,7 +58,7 @@ const allow = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +69,6 @@ const deny = () => {
|
||||
</script>
|
||||
|
||||
<Modal bind:this={modal} title="Affiliate sponsoring request" width="700px">
|
||||
<LoadingIndicator bind:loading={loading} />
|
||||
<section>
|
||||
<p>
|
||||
Hi! {referral} wants you to sponsor their pixeldrain account. This
|
||||
|
@@ -2,24 +2,22 @@
|
||||
import { onMount } from "svelte";
|
||||
import Pro from "icons/Pro.svelte";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import ProgressBar from "util/ProgressBar.svelte";
|
||||
import SuccessMessage from "util/SuccessMessage.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = false
|
||||
let success_message
|
||||
let hotlinking = window.user.hotlinking_enabled
|
||||
let transfer_cap = window.user.monthly_transfer_cap / 1e12
|
||||
let skip_viewer = window.user.skip_file_viewer
|
||||
|
||||
const update = async () => {
|
||||
loading = true
|
||||
|
||||
const form = new FormData()
|
||||
form.append("hotlinking_enabled", hotlinking)
|
||||
form.append("transfer_cap", transfer_cap*1e12)
|
||||
form.append("skip_file_viewer", skip_viewer)
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/user",
|
||||
@@ -37,7 +35,7 @@ const update = async () => {
|
||||
} catch (err) {
|
||||
success_message.set(false, "Failed to update subscription: "+err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +76,6 @@ onMount(() => {
|
||||
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h2><Pro/>Hotlinking (bandwidth sharing)</h2>
|
||||
<SuccessMessage bind:this={success_message}></SuccessMessage>
|
||||
|
@@ -1,13 +1,12 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = false
|
||||
let app_name = ""
|
||||
let api_key = ""
|
||||
const create_key = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
try {
|
||||
let form = new FormData()
|
||||
form.append("app_name", app_name)
|
||||
@@ -27,7 +26,7 @@ const create_key = async () => {
|
||||
} catch (err) {
|
||||
alert("Failed to create new API key! "+err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +47,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<!-- Show a back button if an app is selected -->
|
||||
{#if app_name}
|
||||
|
@@ -1,16 +1,13 @@
|
||||
<script>
|
||||
import Checkout from "layout/checkout/Checkout.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { onMount } from "svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
|
||||
let loading = false
|
||||
let credit_amount = 10
|
||||
|
||||
const checkout = async (network = "", amount = 0, country = "") => {
|
||||
loading = true
|
||||
|
||||
if (amount < 10) {
|
||||
alert("Amount needs to be at least €10")
|
||||
return
|
||||
@@ -21,6 +18,7 @@ const checkout = async (network = "", amount = 0, country = "") => {
|
||||
form.set("network", network)
|
||||
form.set("country", country)
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/user/invoice",
|
||||
@@ -35,14 +33,14 @@ const checkout = async (network = "", amount = 0, country = "") => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
let invoices = []
|
||||
let unpaid_invoice = 0
|
||||
const load_invoices = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/invoice")
|
||||
if(resp.status >= 400) {
|
||||
@@ -66,7 +64,7 @@ const load_invoices = async () => {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,8 +73,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h2 id="deposit">Deposit credit</h2>
|
||||
<p>
|
||||
|
@@ -1,20 +1,19 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import Persistence from "icons/Persistence.svelte";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import SuccessMessage from "util/SuccessMessage.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = false
|
||||
let success_message
|
||||
|
||||
// Embedding settings
|
||||
let embed_domains = ""
|
||||
|
||||
const save_embed = async () => {
|
||||
loading = true
|
||||
const form = new FormData()
|
||||
form.append("embed_domains", embed_domains)
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/user",
|
||||
@@ -30,7 +29,7 @@ const save_embed = async () => {
|
||||
} catch(err) {
|
||||
success_message.set(false, err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +38,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h2><Persistence/>Embedding controls</h2>
|
||||
<SuccessMessage bind:this={success_message}></SuccessMessage>
|
||||
|
@@ -1,20 +1,18 @@
|
||||
<script>
|
||||
import Euro from "util/Euro.svelte"
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import SuccessMessage from "util/SuccessMessage.svelte";
|
||||
import PatreonActivationResult from "./PatreonActivationResult.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = false
|
||||
let subscription = window.user.subscription.id
|
||||
let subscription_type = window.user.subscription.type
|
||||
let success_message
|
||||
|
||||
const update = async (plan) => {
|
||||
loading = true
|
||||
|
||||
const form = new FormData()
|
||||
form.append("subscription", plan)
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
{
|
||||
const resp = await fetch(
|
||||
@@ -43,13 +41,11 @@ const update = async (plan) => {
|
||||
} catch (err) {
|
||||
success_message.set(false, "Failed to update subscription: "+err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
{#if window.location.hash === "#checkout_complete"}
|
||||
<div class="highlight_green">
|
||||
|
@@ -2,9 +2,7 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import Euro from "util/Euro.svelte"
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
|
||||
let loading = false
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
@@ -22,7 +20,7 @@ let transactions = {
|
||||
}
|
||||
|
||||
const load_transactions = async () => {
|
||||
loading = true
|
||||
loading_start()
|
||||
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/transactions/"+month_str)
|
||||
@@ -67,7 +65,7 @@ const load_transactions = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
const last_month = () => {
|
||||
@@ -97,8 +95,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<section>
|
||||
<h2>Transaction log</h2>
|
||||
<p>
|
||||
|
@@ -1,10 +1,8 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Button from "layout/Button.svelte";
|
||||
|
||||
let loading = false
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
@@ -12,8 +10,8 @@ let month_str = ""
|
||||
let data = []
|
||||
|
||||
const load_activity = async () => {
|
||||
loading = true
|
||||
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/activity/" + month_str)
|
||||
if(resp.status >= 400) {
|
||||
@@ -30,7 +28,7 @@ const load_activity = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
const last_month = () => {
|
||||
@@ -60,8 +58,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<div class="toolbar">
|
||||
<Button click={last_month} icon="chevron_left"/>
|
||||
<div class="toolbar_spacer">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { FSNavigator } from "filesystem/FSNavigator.ts"
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "filesystem/FilesystemAPI";
|
||||
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI";
|
||||
import Button from "layout/Button.svelte";
|
||||
import CreateDirectory from "filesystem/filemanager/CreateDirectory.svelte";
|
||||
import FSUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
|
||||
|
@@ -2,10 +2,8 @@
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import Euro from "util/Euro.svelte"
|
||||
import LoadingIndicator from "util/LoadingIndicator.svelte";
|
||||
import Button from "layout/Button.svelte";
|
||||
|
||||
let loading = false
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
@@ -25,8 +23,8 @@ let transactions = {
|
||||
}
|
||||
|
||||
const load_transactions = async () => {
|
||||
loading = true
|
||||
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||
loading_start()
|
||||
try {
|
||||
const resp = await fetch(window.api_endpoint+"/user/transactions/"+month_str)
|
||||
if(resp.status >= 400) {
|
||||
@@ -79,7 +77,7 @@ const load_transactions = async () => {
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
loading = false
|
||||
loading_finish()
|
||||
}
|
||||
};
|
||||
const last_month = () => {
|
||||
@@ -109,8 +107,6 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
|
||||
<div class="toolbar">
|
||||
<Button click={last_month} icon="chevron_left"/>
|
||||
<div class="toolbar_spacer">
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<script>
|
||||
import Spinner from "./Spinner.svelte";
|
||||
|
||||
export let loading = false
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="container">
|
||||
<Spinner/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|