Replace user home page with user dashboard
This commit is contained in:
@@ -78,41 +78,49 @@ const stats_update = () => {
|
|||||||
|
|
||||||
<div class="stats_box">
|
<div class="stats_box">
|
||||||
<div>
|
<div>
|
||||||
Size {formatDataVolume(total_size, 3)}
|
<div>
|
||||||
|
Size {formatDataVolume(total_size, 3)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Progress {(total_progress*100).toPrecision(3)}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Progress {(total_progress*100).toPrecision(3)}%
|
{#if finished}
|
||||||
|
<div>
|
||||||
|
Time {formatDuration(elapsed_time, 0)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Rate {formatDataVolume(total_loaded / (elapsed_time/1000), 3)}/s
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
ETA {formatDuration(remaining_time, 0)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Rate {formatDataVolume(total_rate, 3)}/s
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if finished}
|
|
||||||
<div>
|
|
||||||
Time {formatDuration(elapsed_time, 0)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Rate {formatDataVolume(total_loaded / (elapsed_time/1000), 3)}/s
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div>
|
|
||||||
ETA {formatDuration(remaining_time, 0)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Rate {formatDataVolume(total_rate, 3)}/s
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProgressBar total={total_size} used={total_loaded} animation="linear" speed={stats_interval_ms}/>
|
<ProgressBar total={total_size} used={total_loaded} animation="linear" speed={stats_interval_ms}/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.stats_box {
|
.stats_box {
|
||||||
display: inline-grid;
|
display: inline-flex;
|
||||||
grid-template-columns: 25% 25% 25% 25%;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: sans-serif, monospace;
|
|
||||||
}
|
}
|
||||||
@media (max-width: 1000px) {
|
.stats_box > div {
|
||||||
.stats_box {
|
flex: 1 1 auto;
|
||||||
grid-template-columns: 50% 50%;
|
display: flex;
|
||||||
}
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.stats_box > div > div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -82,5 +82,6 @@ let click_int = e => {
|
|||||||
background: none;
|
background: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--body_text_color);
|
color: var(--body_text_color);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -22,7 +22,7 @@ let form = {
|
|||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
form.append("username", fields.username)
|
form.append("username", fields.username)
|
||||||
form.append("password", fields.password)
|
form.append("password", fields.password)
|
||||||
form.append("app_name", "website_login")
|
form.append("app_name", "website login")
|
||||||
|
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
window.api_endpoint+"/user/login",
|
window.api_endpoint+"/user/login",
|
||||||
|
@@ -66,7 +66,7 @@ let form = {
|
|||||||
const login_form = new FormData()
|
const login_form = new FormData()
|
||||||
login_form.append("username", fields.username)
|
login_form.append("username", fields.username)
|
||||||
login_form.append("password", fields.password)
|
login_form.append("password", fields.password)
|
||||||
login_form.append("app_name", "website_login")
|
login_form.append("app_name", "website login")
|
||||||
|
|
||||||
const login_resp = await fetch(
|
const login_resp = await fetch(
|
||||||
window.api_endpoint+"/user/login",
|
window.api_endpoint+"/user/login",
|
||||||
|
@@ -1,341 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte";
|
|
||||||
import Chart from "../util/Chart.svelte";
|
|
||||||
import StorageProgressBar from "./StorageProgressBar.svelte";
|
|
||||||
import HotlinkProgressBar from "./HotlinkProgressBar.svelte";
|
|
||||||
import Euro from "../util/Euro.svelte"
|
|
||||||
import { color_by_name } from "../util/Util.svelte";
|
|
||||||
|
|
||||||
let graph_views_downloads = null
|
|
||||||
let graph_bandwidth = null
|
|
||||||
let time_start = ""
|
|
||||||
let time_end = ""
|
|
||||||
|
|
||||||
let load_graphs = async (minutes, interval) => {
|
|
||||||
let end = new Date()
|
|
||||||
let start = new Date()
|
|
||||||
start.setMinutes(start.getMinutes() - minutes)
|
|
||||||
|
|
||||||
try {
|
|
||||||
let views = get_graph_data("views", start, end, interval);
|
|
||||||
let downloads = get_graph_data("downloads", start, end, interval);
|
|
||||||
let bandwidth = get_graph_data("bandwidth", start, end, interval);
|
|
||||||
let transfer_paid = get_graph_data("transfer_paid", start, end, interval);
|
|
||||||
views = await views
|
|
||||||
downloads = await downloads
|
|
||||||
bandwidth = await bandwidth
|
|
||||||
transfer_paid = await transfer_paid
|
|
||||||
|
|
||||||
graph_views_downloads.data().labels = views.timestamps;
|
|
||||||
graph_views_downloads.data().datasets[0].data = views.amounts
|
|
||||||
graph_views_downloads.data().datasets[1].data = downloads.amounts
|
|
||||||
graph_bandwidth.data().labels = bandwidth.timestamps;
|
|
||||||
graph_bandwidth.data().datasets[0].data = bandwidth.amounts
|
|
||||||
graph_bandwidth.data().datasets[1].data = transfer_paid.amounts
|
|
||||||
|
|
||||||
graph_views_downloads.update()
|
|
||||||
graph_bandwidth.update()
|
|
||||||
|
|
||||||
time_start = views.timestamps[0];
|
|
||||||
time_end = views.timestamps.slice(-1)[0];
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to update graphs", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_views = 0
|
|
||||||
let total_downloads = 0
|
|
||||||
let total_bandwidth = 0
|
|
||||||
let total_transfer_paid = 0
|
|
||||||
|
|
||||||
let get_graph_data = async (stat, start, end, interval) => {
|
|
||||||
let resp = await fetch(
|
|
||||||
window.api_endpoint + "/user/time_series/" + stat +
|
|
||||||
"?start=" + start.toISOString() +
|
|
||||||
"&end=" + end.toISOString() +
|
|
||||||
"&interval=" + interval
|
|
||||||
)
|
|
||||||
resp = await resp.json()
|
|
||||||
|
|
||||||
// Convert the timestamps to a human-friendly format
|
|
||||||
resp.timestamps.forEach((val, idx) => {
|
|
||||||
let date = new Date(val);
|
|
||||||
let str = date.getFullYear();
|
|
||||||
str += "-" + ("00" + (date.getMonth() + 1)).slice(-2);
|
|
||||||
str += "-" + ("00" + date.getDate()).slice(-2);
|
|
||||||
str += " " + ("00" + date.getHours()).slice(-2);
|
|
||||||
str += ":" + ("00" + date.getMinutes()).slice(-2);
|
|
||||||
resp.timestamps[idx] = " " + str + " "; // Poor man's padding
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add up the total amount and save it in the correct place
|
|
||||||
let total = resp.amounts.reduce((acc, cur) => { return acc + cur }, 0)
|
|
||||||
|
|
||||||
if (stat == "views") {
|
|
||||||
total_views = total;
|
|
||||||
} else if (stat == "downloads") {
|
|
||||||
total_downloads = total;
|
|
||||||
graph_views_downloads.update()
|
|
||||||
} else if (stat == "bandwidth") {
|
|
||||||
total_bandwidth = total;
|
|
||||||
} else if (stat == "transfer_paid") {
|
|
||||||
total_transfer_paid = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
let graph_timeout = null
|
|
||||||
let graph_timespan = 0
|
|
||||||
let update_graphs = (minutes, interval, live) => {
|
|
||||||
if (graph_timeout !== null) { clearTimeout(graph_timeout) }
|
|
||||||
if (live) {
|
|
||||||
graph_timeout = setTimeout(() => { update_graphs(minutes, interval, true) }, 6000)
|
|
||||||
}
|
|
||||||
|
|
||||||
graph_timespan = minutes
|
|
||||||
|
|
||||||
load_graphs(minutes, interval)
|
|
||||||
load_direct_bw()
|
|
||||||
}
|
|
||||||
|
|
||||||
let transfer_cap = 0
|
|
||||||
let transfer_used = 0
|
|
||||||
let storage_space_used = 0
|
|
||||||
let load_direct_bw = () => {
|
|
||||||
let today = new Date()
|
|
||||||
let start = new Date()
|
|
||||||
start.setDate(start.getDate() - 30)
|
|
||||||
|
|
||||||
fetch(
|
|
||||||
window.api_endpoint + "/user/time_series/transfer_paid" +
|
|
||||||
"?start=" + start.toISOString() +
|
|
||||||
"&end=" + today.toISOString() +
|
|
||||||
"&interval=60"
|
|
||||||
).then(resp => {
|
|
||||||
if (!resp.ok) { return Promise.reject("Error: " + resp.status); }
|
|
||||||
return resp.json();
|
|
||||||
}).then(resp => {
|
|
||||||
let total = resp.amounts.reduce((accum, val) => accum += val, 0);
|
|
||||||
transfer_used = total
|
|
||||||
storage_space_used = window.user.storage_space_used
|
|
||||||
}).catch(e => {
|
|
||||||
console.error("Error requesting time series: " + e);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (window.user.monthly_transfer_cap > 0) {
|
|
||||||
transfer_cap = window.user.monthly_transfer_cap
|
|
||||||
} else if (window.user.subscription.monthly_transfer_cap > 0) {
|
|
||||||
transfer_cap = window.user.subscription.monthly_transfer_cap
|
|
||||||
} else {
|
|
||||||
transfer_cap = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
graph_views_downloads.data().datasets = [
|
|
||||||
{
|
|
||||||
label: "Views",
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: color_by_name("highlight_color"),
|
|
||||||
backgroundColor: color_by_name("highlight_color"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Downloads",
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: color_by_name("danger_color"),
|
|
||||||
backgroundColor: color_by_name("danger_color"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
graph_bandwidth.data().datasets = [
|
|
||||||
{
|
|
||||||
label: "Total bandwidth",
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: color_by_name("highlight_color"),
|
|
||||||
backgroundColor: color_by_name("highlight_color"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Premium bandwidth",
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 0,
|
|
||||||
borderColor: color_by_name("danger_color"),
|
|
||||||
backgroundColor: color_by_name("danger_color"),
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
update_graphs(43200, 1440, true);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (graph_timeout !== null) {
|
|
||||||
clearTimeout(graph_timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Account information</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Username: {window.user.username}</li>
|
|
||||||
<li>
|
|
||||||
{#if window.user.email === ""}
|
|
||||||
No e-mail address configured. You will not be able to recover
|
|
||||||
your account if you lose your password. Set an e-mail address on
|
|
||||||
the <a href="/user/settings">settings page</a>.
|
|
||||||
{:else}
|
|
||||||
E-mail address: {window.user.email}
|
|
||||||
(<a href="/user/settings">configure</a>)
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Supporter level: {window.user.subscription.name}
|
|
||||||
(<a href="/user/subscription">manage subscriptions</a> /
|
|
||||||
<a href="/api/patreon_auth/start">link patreon subscription</a>)
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Max file size: {formatDataVolume(window.user.subscription.file_size_limit, 3)}
|
|
||||||
</li>
|
|
||||||
{#if window.user.subscription.file_expiry_days > 0}
|
|
||||||
<li>Files expire after {window.user.subscription.file_expiry_days} days</li>
|
|
||||||
{:else}
|
|
||||||
<li>Files never expire</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Current account balance: <Euro amount={window.user.balance_micro_eur}></Euro>
|
|
||||||
(<a href="/user/prepaid/deposit">deposit credit</a> /
|
|
||||||
<a href="/user/prepaid/transactions">transaction log</a>)
|
|
||||||
|
|
||||||
{#if window.user.balance_micro_eur > 0 && window.user.subscription.id === ""}
|
|
||||||
<br/>
|
|
||||||
You have account credit but no active subscription. Activate
|
|
||||||
a subscription on the <a href="/user/subscription">subscriptions page</a>
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{#if window.user.subscription.storage_space === -1}
|
|
||||||
Storage space used: {formatDataVolume(storage_space_used, 3)}<br/>
|
|
||||||
{:else}
|
|
||||||
<StorageProgressBar used={storage_space_used} total={window.user.subscription.storage_space}></StorageProgressBar>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if transfer_cap === -1}
|
|
||||||
Premium transfer used in the last 30 days: {formatDataVolume(transfer_used, 3)}
|
|
||||||
(<a href="/user/sharing/bandwidth">configure limit</a>)
|
|
||||||
{:else}
|
|
||||||
Transfer cap:
|
|
||||||
{formatDataVolume(transfer_used, 3)}
|
|
||||||
out of
|
|
||||||
{formatDataVolume(transfer_cap, 3)}
|
|
||||||
(<a href="/user/sharing/bandwidth">Set your transfer limit on the sharing page</a>)
|
|
||||||
<HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<h3>Exports</h3>
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<a href="/user/export/files" class="button">
|
|
||||||
<i class="icon">list</i>
|
|
||||||
Export uploaded files to CSV
|
|
||||||
</a>
|
|
||||||
<a href="/user/export/lists" class="button">
|
|
||||||
<i class="icon">list</i>
|
|
||||||
Export created lists to CSV
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Statistics</h2>
|
|
||||||
<p>
|
|
||||||
Here you can see how often your files are viewed, downloaded and how
|
|
||||||
much bandwidth they consume. The buttons below can be pressed to adjust
|
|
||||||
the timeframe.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="highlight_border">
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(1440, 1, true)}
|
|
||||||
class:button_highlight={graph_timespan == 1440}>
|
|
||||||
Day (1m)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(10080, 60, true)}
|
|
||||||
class:button_highlight={graph_timespan == 10080}>
|
|
||||||
Week (1h)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(20160, 60, true)}
|
|
||||||
class:button_highlight={graph_timespan == 20160}>
|
|
||||||
Two Weeks (1h)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(43200, 1440, false)}
|
|
||||||
class:button_highlight={graph_timespan == 43200}>
|
|
||||||
Month (1d)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(131400, 1440, false)}
|
|
||||||
class:button_highlight={graph_timespan == 131400}>
|
|
||||||
Quarter (1d)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(525600, 1440, false)}
|
|
||||||
class:button_highlight={graph_timespan == 525600}>
|
|
||||||
Year (1d)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
on:click={() => update_graphs(1051200, 1440, false)}
|
|
||||||
class:button_highlight={graph_timespan == 1051200}>
|
|
||||||
Two Years (1d)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Premium transfers and total bandwidth usage</h3>
|
|
||||||
<p>
|
|
||||||
Total bandwidth usage is the combined bandwidth usage of all the files
|
|
||||||
on your account. This includes paid transfers.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
A premium transfer is when a file is downloaded using the data cap on
|
|
||||||
your subscription plan. These can be files you downloaded from other
|
|
||||||
people, or other people downloading your files if you have bandwidth
|
|
||||||
sharing enabled. Bandwidth sharing can be changed on
|
|
||||||
<a href="/user/sharing/bandwidth">the sharing page</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Chart bind:this={graph_bandwidth} data_type="bytes"/>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="highlight_border">
|
|
||||||
Total usage from {time_start} to {time_end}<br/>
|
|
||||||
{formatDataVolume(total_bandwidth, 3)} bandwidth,
|
|
||||||
{formatDataVolume(total_transfer_paid, 3)} paid transfers
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Views and downloads</h3>
|
|
||||||
<p>
|
|
||||||
A view is counted when someone visits the download page of one of
|
|
||||||
your files. Views are unique per user per file.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Downloads are counted when a user clicks the download button on one
|
|
||||||
of your files. It does not matter whether the download is completed
|
|
||||||
or not, only the start of the download is counted.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Chart bind:this={graph_views_downloads} data_type="number"/>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="highlight_border">
|
|
||||||
Total usage from {time_start} to {time_end}<br/>
|
|
||||||
{formatThousands(total_views)} views and
|
|
||||||
{formatThousands(total_downloads)} downloads
|
|
||||||
</div>
|
|
||||||
</section>
|
|
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import ProgressBar from "../util/ProgressBar.svelte";
|
import ProgressBar from "../util/ProgressBar.svelte";
|
||||||
|
import { formatDataVolume } from "../util/Formatting.svelte"
|
||||||
|
|
||||||
export let total = 0
|
export let total = 0
|
||||||
export let used = 0
|
export let used = 0
|
||||||
@@ -8,6 +9,18 @@ $: frac = used / total
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ProgressBar total={total} used={used}></ProgressBar>
|
<ProgressBar total={total} used={used}></ProgressBar>
|
||||||
|
<div class="gauge_labels">
|
||||||
|
<div>
|
||||||
|
{formatDataVolume(used, 3)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if total <= 0}
|
||||||
|
No limit
|
||||||
|
{:else}
|
||||||
|
{formatDataVolume(total, 3)}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if frac > 1}
|
{#if frac > 1}
|
||||||
<div class="highlight_yellow">
|
<div class="highlight_yellow">
|
||||||
@@ -53,3 +66,13 @@ $: frac = used / total
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.gauge_labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.gauge_labels > div {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import Home from "./Home.svelte";
|
|
||||||
import AccountSettings from "./AccountSettings.svelte";
|
import AccountSettings from "./AccountSettings.svelte";
|
||||||
import APIKeys from "./APIKeys.svelte";
|
import APIKeys from "./APIKeys.svelte";
|
||||||
import Transactions from "./Transactions.svelte";
|
import Transactions from "./Transactions.svelte";
|
||||||
@@ -11,13 +10,15 @@ import TabMenu from "../util/TabMenu.svelte";
|
|||||||
import BandwidthSharing from "./BandwidthSharing.svelte";
|
import BandwidthSharing from "./BandwidthSharing.svelte";
|
||||||
import EmbeddingControls from "./EmbeddingControls.svelte";
|
import EmbeddingControls from "./EmbeddingControls.svelte";
|
||||||
import PageBranding from "./PageBranding.svelte";
|
import PageBranding from "./PageBranding.svelte";
|
||||||
|
import Dashboard from "./dashboard/Dashboard.svelte";
|
||||||
|
|
||||||
let pages = [
|
let pages = [
|
||||||
{
|
{
|
||||||
path: "/user/home",
|
path: "/user",
|
||||||
title: "My Home",
|
title: "Dashboard",
|
||||||
icon: "home",
|
icon: "dashboard",
|
||||||
component: Home,
|
component: Dashboard,
|
||||||
|
hide_background: true,
|
||||||
}, {
|
}, {
|
||||||
path: "/user/settings",
|
path: "/user/settings",
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
|
@@ -7,12 +7,19 @@ export let used = 0
|
|||||||
$: frac = used / total
|
$: frac = used / total
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
Storage:
|
|
||||||
{formatDataVolume(used, 3)}
|
|
||||||
out of
|
|
||||||
{formatDataVolume(total, 3)}
|
|
||||||
<br/>
|
|
||||||
<ProgressBar total={total} used={used}></ProgressBar>
|
<ProgressBar total={total} used={used}></ProgressBar>
|
||||||
|
<div class="gauge_labels">
|
||||||
|
<div>
|
||||||
|
{formatDataVolume(used, 3)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if total <= 0}
|
||||||
|
No limit
|
||||||
|
{:else}
|
||||||
|
{formatDataVolume(total, 3)}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if frac > 2.0}
|
{#if frac > 2.0}
|
||||||
<div class="highlight_red">
|
<div class="highlight_red">
|
||||||
@@ -61,4 +68,13 @@ out of
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gauge_labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
.gauge_labels > div {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
67
svelte/src/user_home/dashboard/CardAccount.svelte
Normal file
67
svelte/src/user_home/dashboard/CardAccount.svelte
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<ul>
|
||||||
|
<li>Username: {window.user.username}</li>
|
||||||
|
<li>
|
||||||
|
{#if window.user.email === ""}
|
||||||
|
No e-mail address configured. You will not be able to recover
|
||||||
|
your account if you lose your password.
|
||||||
|
{:else}
|
||||||
|
E-mail address: {window.user.email}
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="icon">settings</i>
|
||||||
|
<a href="/user/settings">Account settings</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Quick navigation</h3>
|
||||||
|
|
||||||
|
<div class="button_row">
|
||||||
|
<a href="/user/filemanager#files" class="button">
|
||||||
|
<i class="icon">image</i>
|
||||||
|
My Files
|
||||||
|
</a>
|
||||||
|
<a href="/user/filemanager#lists" class="button">
|
||||||
|
<i class="icon">photo_library</i>
|
||||||
|
My Albums
|
||||||
|
</a>
|
||||||
|
{#if window.user.subscription.filesystem_access}
|
||||||
|
<a href="/d/me" class="button">
|
||||||
|
<i class="icon">folder</i>
|
||||||
|
Filesystem
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<a href="/logout" class="button">
|
||||||
|
<i class="icon">logout</i>
|
||||||
|
Log out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Exports</h3>
|
||||||
|
|
||||||
|
<div class="button_row">
|
||||||
|
<a href="/user/export/files" class="button">
|
||||||
|
<i class="icon">list</i>
|
||||||
|
Export files to CSV
|
||||||
|
</a>
|
||||||
|
<a href="/user/export/lists" class="button">
|
||||||
|
<i class="icon">list</i>
|
||||||
|
Export albums to CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button_row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.button_row > a {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
</style>
|
138
svelte/src/user_home/dashboard/CardActivity.svelte
Normal file
138
svelte/src/user_home/dashboard/CardActivity.svelte
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { formatDate } from "../../util/Formatting.svelte";
|
||||||
|
import LoadingIndicator from "../../util/LoadingIndicator.svelte";
|
||||||
|
import Button from "../../layout/Button.svelte";
|
||||||
|
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
let year = 0
|
||||||
|
let month = 0
|
||||||
|
let month_str = ""
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
const load_activity = async () => {
|
||||||
|
loading = true
|
||||||
|
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||||
|
try {
|
||||||
|
const resp = await fetch(window.api_endpoint+"/user/activity/" + month_str)
|
||||||
|
if(resp.status >= 400) {
|
||||||
|
let json = await resp.json()
|
||||||
|
if (json.value === "authentication_failed") {
|
||||||
|
window.location = "/login"
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await resp.json()
|
||||||
|
} catch (err) {
|
||||||
|
alert(err)
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const last_month = () => {
|
||||||
|
month--
|
||||||
|
if (month === 0) {
|
||||||
|
month = 12
|
||||||
|
year--
|
||||||
|
}
|
||||||
|
|
||||||
|
load_activity()
|
||||||
|
}
|
||||||
|
const next_month = () => {
|
||||||
|
month++
|
||||||
|
if (month === 13) {
|
||||||
|
month = 1
|
||||||
|
year++
|
||||||
|
}
|
||||||
|
|
||||||
|
load_activity()
|
||||||
|
}
|
||||||
|
onMount(() => {
|
||||||
|
let now = new Date()
|
||||||
|
year = now.getFullYear()
|
||||||
|
month = now.getMonth()+1
|
||||||
|
|
||||||
|
load_activity()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LoadingIndicator loading={loading}/>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<Button click={last_month} icon="chevron_left"/>
|
||||||
|
<div class="toolbar_spacer">
|
||||||
|
{month_str}
|
||||||
|
</div>
|
||||||
|
<Button click={next_month} icon="chevron_right"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if data.length === 0}
|
||||||
|
<div class="center">
|
||||||
|
Removed or expired files will show up here
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="table_scroll">
|
||||||
|
<table style="text-align: left;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Time</td>
|
||||||
|
<td>File name</td>
|
||||||
|
<td>Event</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each data as row}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{formatDate(row.time, true, true, false)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{#if row.event === "file_instance_blocked"}
|
||||||
|
<a href="/u/{row.file_id}">{row.file_name}</a>
|
||||||
|
{:else if row.event === "filesystem_node_blocked"}
|
||||||
|
<a href="/d/{row.file_id}">{row.file_name}</a>
|
||||||
|
{:else}
|
||||||
|
{row.file_name}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{#if row.event === "file_instance_blocked" || row.event === "filesystem_node_blocked"}
|
||||||
|
Blocked for abuse
|
||||||
|
{:else if row.event === "file_instance_expired"}
|
||||||
|
Expired
|
||||||
|
{:else if row.event === "file_instance_lost"}
|
||||||
|
File has been lost
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.toolbar > * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.toolbar_spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
142
svelte/src/user_home/dashboard/CardPrepaidTransactions.svelte
Normal file
142
svelte/src/user_home/dashboard/CardPrepaidTransactions.svelte
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { formatDataVolume, formatDate } from "../../util/Formatting.svelte";
|
||||||
|
import Euro from "../../util/Euro.svelte"
|
||||||
|
import LoadingIndicator from "../../util/LoadingIndicator.svelte";
|
||||||
|
import Button from "../../layout/Button.svelte";
|
||||||
|
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
let year = 0
|
||||||
|
let month = 0
|
||||||
|
let month_str = ""
|
||||||
|
let transactions = {
|
||||||
|
rows: [],
|
||||||
|
total_subscription_charge: 0,
|
||||||
|
total_storage_used: 0,
|
||||||
|
total_storage_charge: 0,
|
||||||
|
total_bandwidth_used: 0,
|
||||||
|
total_bandwidth_charge: 0,
|
||||||
|
total_deposited: 0,
|
||||||
|
total_deducted: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
const load_transactions = async () => {
|
||||||
|
loading = true
|
||||||
|
month_str = year + "-" + ("00"+(month)).slice(-2)
|
||||||
|
try {
|
||||||
|
const resp = await fetch(window.api_endpoint+"/user/transactions/"+month_str)
|
||||||
|
if(resp.status >= 400) {
|
||||||
|
let json = await resp.json()
|
||||||
|
if (json.value === "authentication_failed") {
|
||||||
|
window.location = "/login"
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let month = {
|
||||||
|
rows: await resp.json(),
|
||||||
|
total_subscription_charge: 0,
|
||||||
|
total_storage_used: 0,
|
||||||
|
total_storage_charge: 0,
|
||||||
|
total_bandwidth_used: 0,
|
||||||
|
total_bandwidth_charge: 0,
|
||||||
|
total_deposited: 0,
|
||||||
|
total_deducted: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
month.rows.forEach(row => {
|
||||||
|
row.time = new Date(row.time)
|
||||||
|
month.total_deposited += row.deposit_amount
|
||||||
|
month.total_subscription_charge += row.subscription_charge
|
||||||
|
month.total_storage_used += row.storage_used
|
||||||
|
month.total_storage_charge += row.storage_charge
|
||||||
|
month.total_bandwidth_used += row.bandwidth_used
|
||||||
|
month.total_bandwidth_charge += row.bandwidth_charge
|
||||||
|
month.total_deducted += row.subscription_charge + row.storage_charge + row.bandwidth_charge
|
||||||
|
})
|
||||||
|
|
||||||
|
// Divide the total storage usage by the number of days in a month
|
||||||
|
month.total_storage_used /= 30.4375
|
||||||
|
|
||||||
|
transactions = month
|
||||||
|
} catch (err) {
|
||||||
|
alert(err)
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const last_month = () => {
|
||||||
|
month--
|
||||||
|
if (month === 0) {
|
||||||
|
month = 12
|
||||||
|
year--
|
||||||
|
}
|
||||||
|
|
||||||
|
load_transactions()
|
||||||
|
}
|
||||||
|
const next_month = () => {
|
||||||
|
month++
|
||||||
|
if (month === 13) {
|
||||||
|
month = 1
|
||||||
|
year++
|
||||||
|
}
|
||||||
|
|
||||||
|
load_transactions()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let now = new Date()
|
||||||
|
year = now.getFullYear()
|
||||||
|
month = now.getMonth()+1
|
||||||
|
load_transactions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LoadingIndicator loading={loading}/>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<Button click={last_month} icon="chevron_left"/>
|
||||||
|
<div class="toolbar_spacer">
|
||||||
|
{month_str}
|
||||||
|
</div>
|
||||||
|
<Button click={next_month} icon="chevron_right"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Total charge: <Euro amount={transactions.total_deducted} precision="4"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Subscription charge: <Euro amount={transactions.total_subscription_charge} precision="4"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Storage charge: <Euro amount={transactions.total_storage_charge} precision="4"/>
|
||||||
|
(used {formatDataVolume(transactions.total_storage_used, 3)})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Bandwidth charge: <Euro amount={transactions.total_bandwidth_charge} precision="4"/>
|
||||||
|
(used {formatDataVolume(transactions.total_bandwidth_used, 3)})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Deposited: <Euro amount={transactions.total_deposited} precision="4"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.toolbar > * { flex: 0 0 auto; }
|
||||||
|
.toolbar_spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
</style>
|
180
svelte/src/user_home/dashboard/CardStatistics.svelte
Normal file
180
svelte/src/user_home/dashboard/CardStatistics.svelte
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import Chart from "../../util/Chart.svelte";
|
||||||
|
import { color_by_name } from "../../util/Util.svelte";
|
||||||
|
import { formatDataVolume, formatThousands } from "../../util/Formatting.svelte";
|
||||||
|
|
||||||
|
let graph_views_downloads = null
|
||||||
|
let graph_bandwidth = null
|
||||||
|
|
||||||
|
let load_graphs = async (minutes, interval) => {
|
||||||
|
let end = new Date()
|
||||||
|
let start = new Date()
|
||||||
|
start.setMinutes(start.getMinutes() - minutes)
|
||||||
|
|
||||||
|
try {
|
||||||
|
let views = get_graph_data("views", start, end, interval);
|
||||||
|
let downloads = get_graph_data("downloads", start, end, interval);
|
||||||
|
let bandwidth = get_graph_data("bandwidth", start, end, interval);
|
||||||
|
let transfer_paid = get_graph_data("transfer_paid", start, end, interval);
|
||||||
|
views = await views
|
||||||
|
downloads = await downloads
|
||||||
|
bandwidth = await bandwidth
|
||||||
|
transfer_paid = await transfer_paid
|
||||||
|
|
||||||
|
graph_views_downloads.data().labels = views.timestamps;
|
||||||
|
graph_views_downloads.data().datasets[0].data = views.amounts
|
||||||
|
graph_views_downloads.data().datasets[1].data = downloads.amounts
|
||||||
|
graph_bandwidth.data().labels = bandwidth.timestamps;
|
||||||
|
graph_bandwidth.data().datasets[0].data = bandwidth.amounts
|
||||||
|
graph_bandwidth.data().datasets[1].data = transfer_paid.amounts
|
||||||
|
|
||||||
|
graph_views_downloads.update()
|
||||||
|
graph_bandwidth.update()
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to update graphs", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_views = 0
|
||||||
|
let total_downloads = 0
|
||||||
|
let total_bandwidth = 0
|
||||||
|
let total_transfer_paid = 0
|
||||||
|
|
||||||
|
let get_graph_data = async (stat, start, end, interval) => {
|
||||||
|
let resp = await fetch(
|
||||||
|
window.api_endpoint + "/user/time_series/" + stat +
|
||||||
|
"?start=" + start.toISOString() +
|
||||||
|
"&end=" + end.toISOString() +
|
||||||
|
"&interval=" + interval
|
||||||
|
)
|
||||||
|
resp = await resp.json()
|
||||||
|
|
||||||
|
// Convert the timestamps to a human-friendly format
|
||||||
|
resp.timestamps.forEach((val, idx) => {
|
||||||
|
let date = new Date(val);
|
||||||
|
let str = date.getFullYear();
|
||||||
|
str += "-" + ("00" + (date.getMonth() + 1)).slice(-2);
|
||||||
|
str += "-" + ("00" + date.getDate()).slice(-2);
|
||||||
|
str += " " + ("00" + date.getHours()).slice(-2);
|
||||||
|
str += ":" + ("00" + date.getMinutes()).slice(-2);
|
||||||
|
resp.timestamps[idx] = " " + str + " "; // Poor man's padding
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add up the total amount and save it in the correct place
|
||||||
|
let total = resp.amounts.reduce((acc, cur) => { return acc + cur }, 0)
|
||||||
|
|
||||||
|
if (stat == "views") {
|
||||||
|
total_views = total;
|
||||||
|
} else if (stat == "downloads") {
|
||||||
|
total_downloads = total;
|
||||||
|
graph_views_downloads.update()
|
||||||
|
} else if (stat == "bandwidth") {
|
||||||
|
total_bandwidth = total;
|
||||||
|
} else if (stat == "transfer_paid") {
|
||||||
|
total_transfer_paid = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
let graph_timespan = 0
|
||||||
|
let update_graphs = (minutes, interval) => {
|
||||||
|
graph_timespan = minutes
|
||||||
|
load_graphs(minutes, interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
graph_views_downloads.data().datasets = [
|
||||||
|
{
|
||||||
|
label: "Views",
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderColor: color_by_name("highlight_color"),
|
||||||
|
backgroundColor: color_by_name("highlight_color"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Downloads",
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderColor: color_by_name("danger_color"),
|
||||||
|
backgroundColor: color_by_name("danger_color"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
graph_bandwidth.data().datasets = [
|
||||||
|
{
|
||||||
|
label: "Total bandwidth",
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderColor: color_by_name("highlight_color"),
|
||||||
|
backgroundColor: color_by_name("highlight_color"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Premium bandwidth",
|
||||||
|
borderWidth: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderColor: color_by_name("danger_color"),
|
||||||
|
backgroundColor: color_by_name("danger_color"),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
update_graphs(43200, 1440);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(1440, 1)}
|
||||||
|
class:button_highlight={graph_timespan == 1440}>
|
||||||
|
Day (1m)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(10080, 60)}
|
||||||
|
class:button_highlight={graph_timespan == 10080}>
|
||||||
|
Week (1h)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(20160, 60)}
|
||||||
|
class:button_highlight={graph_timespan == 20160}>
|
||||||
|
Two Weeks (1h)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(43200, 1440)}
|
||||||
|
class:button_highlight={graph_timespan == 43200}>
|
||||||
|
Month (1d)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(131400, 1440)}
|
||||||
|
class:button_highlight={graph_timespan == 131400}>
|
||||||
|
Quarter (1d)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(525600, 1440)}
|
||||||
|
class:button_highlight={graph_timespan == 525600}>
|
||||||
|
Year (1d)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => update_graphs(1051200, 1440)}
|
||||||
|
class:button_highlight={graph_timespan == 1051200}>
|
||||||
|
Two Years (1d)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Chart bind:this={graph_bandwidth} data_type="bytes" height="200px" ticks={false}/>
|
||||||
|
<div class="center">
|
||||||
|
{formatDataVolume(total_bandwidth, 3)} free downloads and
|
||||||
|
{formatDataVolume(total_transfer_paid, 3)} paid downloads
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Chart bind:this={graph_views_downloads} data_type="number" height="200px" ticks={false}/>
|
||||||
|
<div class="center">
|
||||||
|
{formatThousands(total_views)} views and
|
||||||
|
{formatThousands(total_downloads)} downloads
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
64
svelte/src/user_home/dashboard/CardSubscription.svelte
Normal file
64
svelte/src/user_home/dashboard/CardSubscription.svelte
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script>
|
||||||
|
import Euro from "../../util/Euro.svelte";
|
||||||
|
import { formatDataVolume } from "../../util/Formatting.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Supporter level: {window.user.subscription.name}<br/>
|
||||||
|
<i class="icon">shopping_cart</i>
|
||||||
|
<a href="/user/subscription">Manage subscriptions</a><br/>
|
||||||
|
<i class="icon">add_link</i>
|
||||||
|
<a href="/api/patreon_auth/start">Link Patreon subscription</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{#if window.user.balance_micro_eur !== 0}
|
||||||
|
<li>
|
||||||
|
Current account balance: <Euro amount={window.user.balance_micro_eur}></Euro><br/>
|
||||||
|
|
||||||
|
<i class="icon">account_balance_wallet</i>
|
||||||
|
<a href="/user/prepaid/deposit">Deposit credit</a><br/>
|
||||||
|
|
||||||
|
<i class="icon">receipt</i>
|
||||||
|
<a href="/user/prepaid/transactions">Transaction log</a>
|
||||||
|
|
||||||
|
{#if window.user.balance_micro_eur > 0 && window.user.subscription.id === ""}
|
||||||
|
<br/>
|
||||||
|
You have account credit but no active subscription. Activate
|
||||||
|
a subscription on the <a href="/user/subscription">subscriptions page</a>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Max file size: {formatDataVolume(window.user.subscription.file_size_limit, 3)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{#if window.user.subscription.storage_space > 0}
|
||||||
|
Storage limit: {formatDataVolume(window.user.subscription.storage_space, 3)}
|
||||||
|
{:else}
|
||||||
|
No storage limit
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{#if window.user.subscription.monthly_transfer_cap > 0}
|
||||||
|
Data transfer limit: {formatDataVolume(window.user.subscription.monthly_transfer_cap, 3)}
|
||||||
|
{:else}
|
||||||
|
No data transfer limit
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{#if window.user.subscription.file_expiry_days > 0}
|
||||||
|
Files expire after {window.user.subscription.file_expiry_days} days
|
||||||
|
{:else}
|
||||||
|
Files never expire
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
44
svelte/src/user_home/dashboard/CardUpload.svelte
Normal file
44
svelte/src/user_home/dashboard/CardUpload.svelte
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script>
|
||||||
|
import UploadLib from "./UploadLib.svelte";
|
||||||
|
|
||||||
|
let upload_widget
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="upload_buttons">
|
||||||
|
<button on:click={() => upload_widget.pick_files() } class="big_button button_highlight">
|
||||||
|
<i class="icon small">cloud_upload</i>
|
||||||
|
<span><u>U</u>pload Files</span>
|
||||||
|
</button>
|
||||||
|
<a href="/t" id="upload_text_button" class="button big_button button_highlight">
|
||||||
|
<i class="icon small">text_fields</i>
|
||||||
|
<span>Upload <u>T</u>ext</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<UploadLib bind:this={upload_widget}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.upload_buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.upload_buttons > * {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.big_button {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 300px;
|
||||||
|
font-size: 1.4em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
53
svelte/src/user_home/dashboard/CardUsage.svelte
Normal file
53
svelte/src/user_home/dashboard/CardUsage.svelte
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import HotlinkProgressBar from "../HotlinkProgressBar.svelte";
|
||||||
|
import StorageProgressBar from "../StorageProgressBar.svelte";
|
||||||
|
|
||||||
|
|
||||||
|
let transfer_cap = 0
|
||||||
|
let transfer_used = 0
|
||||||
|
let storage_used = window.user.storage_space_used
|
||||||
|
let storage_limit = window.user.subscription.storage_space
|
||||||
|
let load_direct_bw = () => {
|
||||||
|
let today = new Date()
|
||||||
|
let start = new Date()
|
||||||
|
start.setDate(start.getDate() - 30)
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
window.api_endpoint + "/user/time_series/transfer_paid" +
|
||||||
|
"?start=" + start.toISOString() +
|
||||||
|
"&end=" + today.toISOString() +
|
||||||
|
"&interval=60"
|
||||||
|
).then(resp => {
|
||||||
|
if (!resp.ok) { return Promise.reject("Error: " + resp.status); }
|
||||||
|
return resp.json();
|
||||||
|
}).then(resp => {
|
||||||
|
let total = resp.amounts.reduce((accum, val) => accum += val, 0);
|
||||||
|
transfer_used = total
|
||||||
|
}).catch(e => {
|
||||||
|
console.error("Error requesting time series: " + e);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (window.user.monthly_transfer_cap > 0) {
|
||||||
|
transfer_cap = window.user.monthly_transfer_cap
|
||||||
|
} else if (window.user.subscription.monthly_transfer_cap > 0) {
|
||||||
|
transfer_cap = window.user.subscription.monthly_transfer_cap
|
||||||
|
} else {
|
||||||
|
transfer_cap = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
load_direct_bw()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
Storage space used:
|
||||||
|
<StorageProgressBar used={storage_used} total={storage_limit}/>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
Premium data transfer:
|
||||||
|
(<a href="/user/sharing/bandwidth">set custom limit</a>)
|
||||||
|
<HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar>
|
163
svelte/src/user_home/dashboard/Dashboard.svelte
Normal file
163
svelte/src/user_home/dashboard/Dashboard.svelte
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<script>
|
||||||
|
import Button from "../../layout/Button.svelte"
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import CardAccount from "./CardAccount.svelte";
|
||||||
|
import CardStatistics from "./CardStatistics.svelte";
|
||||||
|
import CardSubscription from "./CardSubscription.svelte";
|
||||||
|
import CardUsage from "./CardUsage.svelte";
|
||||||
|
import CardActivity from "./CardActivity.svelte";
|
||||||
|
import CardUpload from "./CardUpload.svelte";
|
||||||
|
import CardPrepaidTransactions from "./CardPrepaidTransactions.svelte";
|
||||||
|
|
||||||
|
let cards = [
|
||||||
|
{
|
||||||
|
id: "upload",
|
||||||
|
elem: CardUpload,
|
||||||
|
title: "Quick upload",
|
||||||
|
},{
|
||||||
|
id: "account",
|
||||||
|
elem: CardAccount,
|
||||||
|
title: "Account",
|
||||||
|
link: "/user/settings",
|
||||||
|
}, {
|
||||||
|
id: "subscription",
|
||||||
|
elem: CardSubscription,
|
||||||
|
title: "Subscription",
|
||||||
|
link: "/user/subscription",
|
||||||
|
}, {
|
||||||
|
id: "prepaid_transactions",
|
||||||
|
elem: CardPrepaidTransactions,
|
||||||
|
title: "Prepaid transactions",
|
||||||
|
link: "/user/prepaid/transactions",
|
||||||
|
hidden: window.user.subscription.type !== "prepaid"
|
||||||
|
}, {
|
||||||
|
id: "usage",
|
||||||
|
elem: CardUsage,
|
||||||
|
title: "Usage",
|
||||||
|
}, {
|
||||||
|
id: "activiy",
|
||||||
|
elem: CardActivity,
|
||||||
|
title: "Activity",
|
||||||
|
link: "/user/activity",
|
||||||
|
}, {
|
||||||
|
id: "statistics",
|
||||||
|
elem: CardStatistics,
|
||||||
|
title: "Statistics",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const save = () => {
|
||||||
|
let storage = {
|
||||||
|
expanded: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const card of cards) {
|
||||||
|
if (card.expanded === true) {
|
||||||
|
storage.expanded.push(card.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"dashboard_layout",
|
||||||
|
JSON.stringify(storage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
const str = window.localStorage.getItem("dashboard_layout")
|
||||||
|
if (str === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = JSON.parse(str)
|
||||||
|
|
||||||
|
if (storage.expanded) {
|
||||||
|
for (const card of cards) {
|
||||||
|
if (storage.expanded.includes(card.id)) {
|
||||||
|
card.expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the view
|
||||||
|
cards = cards
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
load()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
{#each cards as card (card.id)}{#if !card.hidden}
|
||||||
|
<div class="card" class:card_wide={card.expanded}>
|
||||||
|
<div class="title_box">
|
||||||
|
<h2>
|
||||||
|
{card.title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{#if card.link}
|
||||||
|
<Button
|
||||||
|
link_href={card.link}
|
||||||
|
icon="link"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
click={() => {card.expanded = !card.expanded; save()}}
|
||||||
|
icon={card.expanded ? "fullscreen_exit" : "fullscreen"}
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="card_component">
|
||||||
|
<svelte:component this={card.elem}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin: 0 8px;
|
||||||
|
border-top: 1px solid var(--separator);
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 25em;
|
||||||
|
max-width: 100%;
|
||||||
|
background: var(--body_background);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: initial;
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
.card_component {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.card_wide {
|
||||||
|
flex-basis: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title_box {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-bottom: 1px solid var(--separator);
|
||||||
|
}
|
||||||
|
.title_box > h2 {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>
|
233
svelte/src/user_home/dashboard/UploadLib.svelte
Normal file
233
svelte/src/user_home/dashboard/UploadLib.svelte
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<script>
|
||||||
|
import UploadProgressBar from "../../home_page/UploadProgressBar.svelte"
|
||||||
|
import { tick } from "svelte"
|
||||||
|
import UploadStats from "../../home_page/UploadStats.svelte";
|
||||||
|
|
||||||
|
export const pick_files = () => {
|
||||||
|
file_input_field.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// === UPLOAD LOGIC ===
|
||||||
|
|
||||||
|
let file_input_field
|
||||||
|
const file_input_change = (event) => {
|
||||||
|
// Start uploading the files async
|
||||||
|
upload_files(event.target.files)
|
||||||
|
|
||||||
|
// This resets the file input field
|
||||||
|
file_input_field.nodeValue = ""
|
||||||
|
}
|
||||||
|
let dragging = false
|
||||||
|
const drop = (e) => {
|
||||||
|
dragging = false;
|
||||||
|
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
upload_files(e.dataTransfer.files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const paste = (e) => {
|
||||||
|
if (e.clipboardData.files[0]) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
upload_files(e.clipboardData.files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_uploads = 0
|
||||||
|
let upload_queue = []
|
||||||
|
let state = "idle" // idle, uploading, finished
|
||||||
|
let upload_stats
|
||||||
|
|
||||||
|
const upload_files = async (files) => {
|
||||||
|
if (files.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add files to the queue
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
if (files[i].type === "" && files[i].size === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_queue.push({
|
||||||
|
file: files[i],
|
||||||
|
name: files[i].name,
|
||||||
|
status: "queued",
|
||||||
|
component: null,
|
||||||
|
id: "",
|
||||||
|
total_size: files[i].size,
|
||||||
|
loaded_size: 0,
|
||||||
|
on_finished: finish_upload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassign array and wait for tick to complete. After the tick is completed
|
||||||
|
// each upload progress bar will have bound itself to its array item
|
||||||
|
upload_queue = upload_queue
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
start_upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const start_upload = () => {
|
||||||
|
let finished_count = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < upload_queue.length && active_uploads < 3; i++) {
|
||||||
|
if (upload_queue[i].status == "queued") {
|
||||||
|
active_uploads++
|
||||||
|
upload_queue[i].component.start()
|
||||||
|
} else if (
|
||||||
|
upload_queue[i].status == "finished" ||
|
||||||
|
upload_queue[i].status == "error"
|
||||||
|
) {
|
||||||
|
finished_count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_uploads === 0 && finished_count != 0) {
|
||||||
|
state = "finished"
|
||||||
|
upload_stats.finish()
|
||||||
|
// uploads_finished()
|
||||||
|
} else {
|
||||||
|
state = "uploading"
|
||||||
|
upload_stats.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finish_upload = (file) => {
|
||||||
|
active_uploads--
|
||||||
|
start_upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const leave_confirmation = e => {
|
||||||
|
if (state === "uploading") {
|
||||||
|
e.preventDefault()
|
||||||
|
e.returnValue = "If you close the page your files will stop uploading. Do you want to continue?"
|
||||||
|
return e.returnValue
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === SHARING BUTTONS ===
|
||||||
|
|
||||||
|
let share_link = ""
|
||||||
|
let input_album_name = ""
|
||||||
|
|
||||||
|
let btn_create_list
|
||||||
|
|
||||||
|
const create_list = async (title, anonymous) => {
|
||||||
|
let files = upload_queue.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (curr.status === "finished") {
|
||||||
|
acc.push({"id": curr.id})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const resp = await fetch(
|
||||||
|
window.api_endpoint+"/list",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"title": title,
|
||||||
|
"anonymous": anonymous,
|
||||||
|
"files": files
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if(!resp.ok) {
|
||||||
|
return Promise.reject("HTTP error: "+resp.status)
|
||||||
|
}
|
||||||
|
return await resp.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
const share_mail = () => window.open("mailto:please@set.address?subject=File%20on%20pixeldrain&body=" + share_link)
|
||||||
|
const share_twitter = () => window.open("https://twitter.com/share?url=" + share_link)
|
||||||
|
const share_facebook = () => window.open('https://www.facebook.com/sharer.php?u=' + share_link)
|
||||||
|
const share_reddit = () => window.open('https://www.reddit.com/submit?url=' + share_link)
|
||||||
|
const share_tumblr = () => window.open('https://www.tumblr.com/share/link?url=' + share_link)
|
||||||
|
|
||||||
|
const create_album = () => {
|
||||||
|
if (!input_album_name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
create_list(input_album_name, false).then(resp => {
|
||||||
|
window.location = '/l/' + resp.id
|
||||||
|
}).catch(err => {
|
||||||
|
alert("Failed to create list. Server says this:\n"+err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const keydown = (e) => {
|
||||||
|
if (e.ctrlKey || e.altKey || e.metaKey) {
|
||||||
|
return // prevent custom shortcuts from interfering with system shortcuts
|
||||||
|
}
|
||||||
|
if (document.activeElement.type && document.activeElement.type === "text") {
|
||||||
|
return // Prevent shortcuts from interfering with input fields
|
||||||
|
}
|
||||||
|
switch (e.key) {
|
||||||
|
case "u": file_input_field.click(); break
|
||||||
|
case "l": btn_create_list.click(); break
|
||||||
|
case "e": share_mail(); break
|
||||||
|
case "w": share_twitter(); break
|
||||||
|
case "f": share_facebook(); break
|
||||||
|
case "r": share_reddit(); break
|
||||||
|
case "m": share_tumblr(); break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
on:dragover|preventDefault|stopPropagation={() => { dragging = true }}
|
||||||
|
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }}
|
||||||
|
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }}
|
||||||
|
on:drop={drop}
|
||||||
|
on:paste={paste}
|
||||||
|
on:keydown={keydown}
|
||||||
|
on:beforeunload={leave_confirmation} />
|
||||||
|
|
||||||
|
<input bind:this={file_input_field} on:change={file_input_change} type="file" name="file" multiple="multiple" class="hide"/>
|
||||||
|
|
||||||
|
<UploadStats bind:this={upload_stats} upload_queue={upload_queue}/>
|
||||||
|
|
||||||
|
{#if upload_queue.length > 1}
|
||||||
|
<div class="album_widget">
|
||||||
|
Create an album<br/>
|
||||||
|
<form class="album_name_form" on:submit|preventDefault={create_album}>
|
||||||
|
<div>Name:</div>
|
||||||
|
<input bind:value={input_album_name} type="text" placeholder="My album"/>
|
||||||
|
<button type="submit" disabled={state !== "finished"}>
|
||||||
|
<i class="icon">create_new_folder</i> Create
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div id="file_drop_highlight" class="highlight_green" class:hide={!dragging}>
|
||||||
|
Drop your files to upload them
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each upload_queue as file}
|
||||||
|
<UploadProgressBar bind:this={file.component} job={file}></UploadProgressBar>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.album_widget {
|
||||||
|
display: block;
|
||||||
|
border-bottom: 1px solid var(--separator);
|
||||||
|
}
|
||||||
|
.album_name_form {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -29,6 +29,8 @@ let chart_object
|
|||||||
export let data_type = ""
|
export let data_type = ""
|
||||||
export let legend = true
|
export let legend = true
|
||||||
export let tooltips = true
|
export let tooltips = true
|
||||||
|
export let ticks = true
|
||||||
|
export let height = "300px"
|
||||||
|
|
||||||
export const chart = () => {
|
export const chart = () => {
|
||||||
return chart_object
|
return chart_object
|
||||||
@@ -80,7 +82,7 @@ onMount(() => {
|
|||||||
layout: {
|
layout: {
|
||||||
padding: {
|
padding: {
|
||||||
left: 4,
|
left: 4,
|
||||||
right: 10,
|
right: 4,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
@@ -106,6 +108,7 @@ onMount(() => {
|
|||||||
x: {
|
x: {
|
||||||
display: true,
|
display: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
|
display: ticks,
|
||||||
sampleSize: 1,
|
sampleSize: 1,
|
||||||
padding: 4,
|
padding: 4,
|
||||||
minRotation: 0,
|
minRotation: 0,
|
||||||
@@ -123,7 +126,7 @@ onMount(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container" style="height: {height};">
|
||||||
<canvas bind:this={chart_element}></canvas>
|
<canvas bind:this={chart_element}></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -131,6 +134,5 @@ onMount(() => {
|
|||||||
.chart-container {
|
.chart-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -72,8 +72,8 @@ onMount(() => get_page())
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="page_content" class="page_content">
|
{#if current_page}
|
||||||
{#if current_page}
|
<div id="page_content" class:page_content={current_page.hide_background !== true}>
|
||||||
{#if current_page.subpages}
|
{#if current_page.subpages}
|
||||||
<div class="tab_bar submenu">
|
<div class="tab_bar submenu">
|
||||||
{#each current_page.subpages as page}
|
{#each current_page.subpages as page}
|
||||||
@@ -95,8 +95,8 @@ onMount(() => get_page())
|
|||||||
{:else}
|
{:else}
|
||||||
<svelte:component this={current_page.component} />
|
<svelte:component this={current_page.component} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<Footer/>
|
<Footer/>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user