Update to svelte 5
This commit is contained in:
1710
svelte/package-lock.json
generated
1710
svelte/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
"rollup": "^4.24.4",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"rollup-plugin-svelte": "^7.2.2",
|
||||
"svelte": "^4.2.19"
|
||||
"svelte": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"behave-js": "^1.5.0",
|
||||
|
@@ -1,15 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { formatDate, formatNumber } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let report
|
||||
export let ip_report_count
|
||||
let preview = false
|
||||
let { report, ip_report_count } = $props();
|
||||
let preview = $state(false)
|
||||
|
||||
$: can_grant = report.status !== "granted"
|
||||
$: can_reject = report.status !== "rejected"
|
||||
let can_grant = $derived(report.status !== "granted")
|
||||
let can_reject = $derived(report.status !== "rejected")
|
||||
|
||||
let set_status = async (action, report_type) => {
|
||||
dispatch("resolve_report", {action: action, report_type: report_type})
|
||||
@@ -17,7 +16,8 @@ let set_status = async (action, report_type) => {
|
||||
</script>
|
||||
|
||||
<Expandable expanded={report.status === "pending"} click_expand>
|
||||
<div slot="header" class="header">
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="icon_cell">
|
||||
<img class="file_icon" src={"/api/file/"+report.file.id+"/thumbnail"} alt="File thumbnail"/>
|
||||
</div>
|
||||
@@ -39,35 +39,36 @@ let set_status = async (action, report_type) => {
|
||||
<div class="stats">V<br/>{formatNumber(report.file.views, 3)}</div>
|
||||
<div class="stats">DL<br/>{formatNumber(report.file.bandwidth_used / report.file.size, 3)}</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div class="details">
|
||||
<div class="toolbar">
|
||||
<div class="action_list">
|
||||
<a class="button" target="_blank" href={"/u/"+report.file.id} rel="noreferrer">
|
||||
<i class="icon">open_in_new</i> Open file
|
||||
</a>
|
||||
<button class:button_highlight={preview} on:click={() => {preview = !preview}}>
|
||||
<button class:button_highlight={preview} onclick={() => {preview = !preview}}>
|
||||
<i class="icon">visibility</i> Preview
|
||||
</button>
|
||||
{#if can_grant}
|
||||
<button class="button_highlight" on:click={() => {set_status("grant", report.type)}}>
|
||||
<button class="button_highlight" onclick={() => {set_status("grant", report.type)}}>
|
||||
<i class="icon">done</i> Block ({report.type})
|
||||
</button>
|
||||
{/if}
|
||||
{#if can_reject}
|
||||
<button class="button_red" on:click={() => {set_status("reject", "")}}>
|
||||
<button class="button_red" onclick={() => {set_status("reject", "")}}>
|
||||
<i class="icon">delete</i> Ignore
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="type_list">
|
||||
<button on:click={() => {set_status("grant", "copyright")}}>copyright</button>
|
||||
<button on:click={() => {set_status("grant", "terrorism")}}>terrorism</button>
|
||||
<button on:click={() => {set_status("grant", "gore")}}>gore</button>
|
||||
<button on:click={() => {set_status("grant", "child_abuse")}}>child_abuse</button>
|
||||
<button on:click={() => {set_status("grant", "zoophilia")}}>zoophilia</button>
|
||||
<button on:click={() => {set_status("grant", "malware")}}>malware</button>
|
||||
<button on:click={() => {set_status("grant", "doxing")}}>doxing</button>
|
||||
<button on:click={() => {set_status("grant", "revenge_porn")}}>revenge_porn</button>
|
||||
<button onclick={() => {set_status("grant", "copyright")}}>copyright</button>
|
||||
<button onclick={() => {set_status("grant", "terrorism")}}>terrorism</button>
|
||||
<button onclick={() => {set_status("grant", "gore")}}>gore</button>
|
||||
<button onclick={() => {set_status("grant", "child_abuse")}}>child_abuse</button>
|
||||
<button onclick={() => {set_status("grant", "zoophilia")}}>zoophilia</button>
|
||||
<button onclick={() => {set_status("grant", "malware")}}>malware</button>
|
||||
<button onclick={() => {set_status("grant", "doxing")}}>doxing</button>
|
||||
<button onclick={() => {set_status("grant", "revenge_porn")}}>revenge_porn</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
@@ -101,12 +102,12 @@ let set_status = async (action, report_type) => {
|
||||
<td>{ip_report_count[user_report.ip_address]}</td>
|
||||
<td>
|
||||
{#if can_grant}
|
||||
<button on:click={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "grant"})}>
|
||||
<button onclick={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "grant"})}>
|
||||
Accept all
|
||||
</button>
|
||||
{/if}
|
||||
{#if can_reject}
|
||||
<button on:click={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "reject"})}>
|
||||
<button onclick={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "reject"})}>
|
||||
Ignore all
|
||||
</button>
|
||||
{/if}
|
||||
|
@@ -4,12 +4,12 @@ import AbuseReport from "./AbuseReport.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let reports = []
|
||||
let reports = $state([])
|
||||
|
||||
let startPicker
|
||||
let endPicker
|
||||
let startPicker = $state()
|
||||
let endPicker = $state()
|
||||
|
||||
let tab = "pending"
|
||||
let tab = $state("pending")
|
||||
|
||||
const get_reports = async () => {
|
||||
loading_start()
|
||||
@@ -74,7 +74,7 @@ const get_reports = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
let ip_report_count = {}
|
||||
let ip_report_count = $state({})
|
||||
const count_ip_reports = () => {
|
||||
ip_report_count = {}
|
||||
reports.forEach(v => {
|
||||
@@ -163,19 +163,19 @@ onMount(() => {
|
||||
<div>Range:</div>
|
||||
<input type="date" bind:this={startPicker}/>
|
||||
<input type="date" bind:this={endPicker}/>
|
||||
<button on:click={get_reports}>Go</button>
|
||||
<button onclick={get_reports}>Go</button>
|
||||
</div>
|
||||
|
||||
<div class="tab_bar">
|
||||
<button on:click={() => {tab = "pending"; get_reports()}} class:button_highlight={tab === "pending"}>
|
||||
<button onclick={() => {tab = "pending"; get_reports()}} class:button_highlight={tab === "pending"}>
|
||||
<i class="icon">flag</i>
|
||||
Pending
|
||||
</button>
|
||||
<button on:click={() => {tab = "granted"; get_reports()}} class:button_highlight={tab === "granted"}>
|
||||
<button onclick={() => {tab = "granted"; get_reports()}} class:button_highlight={tab === "granted"}>
|
||||
<i class="icon">flag</i>
|
||||
Granted
|
||||
</button>
|
||||
<button on:click={() => {tab = "rejected"; get_reports()}} class:button_highlight={tab === "rejected"}>
|
||||
<button onclick={() => {tab = "rejected"; get_reports()}} class:button_highlight={tab === "rejected"}>
|
||||
<i class="icon">flag</i>
|
||||
Rejected
|
||||
</button>
|
||||
|
@@ -17,25 +17,25 @@ type Reporter = {
|
||||
last_message_html: string,
|
||||
}
|
||||
|
||||
let reporters: Reporter[] = []
|
||||
$: reporters_pending = reporters.reduce((acc, val) => {
|
||||
let reporters: Reporter[] = $state([])
|
||||
let reporters_pending = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "pending") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
$: reporters_trusted = reporters.reduce((acc, val) => {
|
||||
}, []))
|
||||
let reporters_trusted = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "trusted") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
$: reporters_rejected = reporters.reduce((acc, val) => {
|
||||
}, []))
|
||||
let reporters_rejected = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "rejected") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}, []))
|
||||
|
||||
const get_reporters = async () => {
|
||||
loading_start()
|
||||
@@ -52,13 +52,14 @@ const get_reporters = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
let edit_button: HTMLButtonElement
|
||||
let creating = false
|
||||
let new_reporter_from_address: HTMLInputElement
|
||||
let new_reporter_name: HTMLInputElement
|
||||
let new_reporter_status = "trusted"
|
||||
let edit_button: HTMLButtonElement = $state()
|
||||
let creating = $state(false)
|
||||
let new_reporter_from_address: HTMLInputElement = $state()
|
||||
let new_reporter_name: HTMLInputElement = $state()
|
||||
let new_reporter_status = $state("trusted")
|
||||
|
||||
const create_reporter = async () => {
|
||||
const create_reporter = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (!new_reporter_from_address.value) {
|
||||
alert("Please enter an e-mail address")
|
||||
return
|
||||
@@ -139,16 +140,16 @@ onMount(get_reporters);
|
||||
<section>
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={() => get_reporters()}>
|
||||
<button onclick={() => get_reporters()}>
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
<button bind:this={edit_button} class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||
<button bind:this={edit_button} class:button_highlight={creating} onclick={() => {creating = !creating}}>
|
||||
<i class="icon">create</i> Add abuse reporter
|
||||
</button>
|
||||
</div>
|
||||
{#if creating}
|
||||
<div class="highlight_shaded">
|
||||
<form on:submit|preventDefault={create_reporter}>
|
||||
<form onsubmit={create_reporter}>
|
||||
<div class="form">
|
||||
<label for="field_from_address">E-mail address</label>
|
||||
<input id="field_from_address" type="text" bind:this={new_reporter_from_address}/>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Modal from "util/Modal.svelte"
|
||||
@@ -7,13 +8,12 @@ import { flip } from "svelte/animate";
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let reporters = []
|
||||
let { reporters = $bindable([]) } = $props();
|
||||
|
||||
$: update_table(reporters)
|
||||
const update_table = (reporters) => sort("")
|
||||
|
||||
let sort_field = "last_used"
|
||||
let asc = false
|
||||
let sort_field = $state("last_used")
|
||||
let asc = $state(false)
|
||||
const sort = (field) => {
|
||||
if (field !== "" && field === sort_field) asc = !asc
|
||||
if (field === "") field = sort_field
|
||||
@@ -40,16 +40,19 @@ const sort = (field) => {
|
||||
reporters = reporters
|
||||
}
|
||||
|
||||
let modal
|
||||
let preview_subject = ""
|
||||
let preview_html = ""
|
||||
let preview_text = ""
|
||||
let modal: Modal = $state()
|
||||
let preview_subject = $state("")
|
||||
let preview_html = $state("")
|
||||
let preview_text = $state("")
|
||||
const toggle_preview = (rep) => {
|
||||
preview_subject = rep.last_message_subject
|
||||
preview_text = rep.last_message_text
|
||||
preview_html = rep.last_message_html
|
||||
modal.show()
|
||||
}
|
||||
run(() => {
|
||||
update_table(reporters)
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="table_scroll">
|
||||
@@ -75,23 +78,23 @@ const toggle_preview = (rep) => {
|
||||
<td>{formatDate(rep.last_used, true, true, false)}</td>
|
||||
<td>{formatDate(rep.created, false, false, false)}</td>
|
||||
<td>
|
||||
<button on:click|preventDefault={() => toggle_preview(rep)} class="button round">
|
||||
<button onclick={preventDefault(() => toggle_preview(rep))} class="button round">
|
||||
<i class="icon">email</i>
|
||||
</button>
|
||||
<button on:click|preventDefault={() => {dispatch("edit", rep)}} class="button round">
|
||||
<button onclick={preventDefault(() => {dispatch("edit", rep)})} class="button round">
|
||||
<i class="icon">edit</i>
|
||||
</button>
|
||||
{#if rep.status !== "trusted"}
|
||||
<button on:click|preventDefault={() => {dispatch("approve", rep)}} class="button button_highlight round">
|
||||
<button onclick={preventDefault(() => {dispatch("approve", rep)})} class="button button_highlight round">
|
||||
<i class="icon">check</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if rep.status !== "rejected"}
|
||||
<button on:click|preventDefault={() => {dispatch("spam", rep)}} class="button button_red round">
|
||||
<button onclick={preventDefault(() => {dispatch("spam", rep)})} class="button button_red round">
|
||||
<i class="icon">block</i>
|
||||
</button>
|
||||
{/if}
|
||||
<button on:click|preventDefault={() => {dispatch("delete", rep)}} class="button button_red round">
|
||||
<button onclick={preventDefault(() => {dispatch("delete", rep)})} class="button button_red round">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</td>
|
||||
|
@@ -2,20 +2,20 @@
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { formatDataVolume, formatThousands, formatDate, formatNumber, formatDuration } from "util/Formatting";
|
||||
import Chart from "util/Chart.svelte";
|
||||
import { color_by_name } from "util/Util.svelte";
|
||||
import { color_by_name } from "util/Util";
|
||||
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
||||
import PeerTable from "./PeerTable.svelte";
|
||||
|
||||
let graphViews
|
||||
let graphBandwidth
|
||||
let graphViews = $state()
|
||||
let graphBandwidth = $state()
|
||||
let graphTimeout = null
|
||||
|
||||
let start_time = ""
|
||||
let end_time = ""
|
||||
let total_bandwidth = 0
|
||||
let total_bandwidth_paid = 0
|
||||
let total_views = 0
|
||||
let total_downloads = 0
|
||||
let start_time = $state("")
|
||||
let end_time = $state("")
|
||||
let total_bandwidth = $state(0)
|
||||
let total_bandwidth_paid = $state(0)
|
||||
let total_views = $state(0)
|
||||
let total_downloads = $state(0)
|
||||
const loadGraph = (minutes, interval, live) => {
|
||||
if (graphTimeout !== null) { clearTimeout(graphTimeout) }
|
||||
if (live) {
|
||||
@@ -64,8 +64,8 @@ const loadGraph = (minutes, interval, live) => {
|
||||
|
||||
// Load performance statistics
|
||||
|
||||
let lastOrder;
|
||||
let status = {
|
||||
let lastOrder = $state();
|
||||
let status = $state({
|
||||
cpu_profile_running_since: "",
|
||||
db_latency: 0,
|
||||
db_time: "",
|
||||
@@ -93,9 +93,9 @@ let status = {
|
||||
rate_limit_watcher_listeners: 0,
|
||||
download_clients: 0,
|
||||
download_connections: 0,
|
||||
}
|
||||
$: total_reads = status.local_reads + status.neighbour_reads + status.remote_reads
|
||||
$: total_read_size = status.local_read_size + status.neighbour_read_size + status.remote_read_size
|
||||
})
|
||||
let total_reads = $derived(status.local_reads + status.neighbour_reads + status.remote_reads)
|
||||
let total_read_size = $derived(status.local_read_size + status.neighbour_read_size + status.remote_read_size)
|
||||
|
||||
function getStats(order) {
|
||||
lastOrder = order
|
||||
@@ -179,14 +179,14 @@ onDestroy(() => {
|
||||
<h3>Bandwidth usage and file views</h3>
|
||||
</section>
|
||||
<div class="highlight_border" style="margin-bottom: 6px;">
|
||||
<button on:click={() => loadGraph(1440, 1, true)}>Day 1m</button>
|
||||
<button on:click={() => loadGraph(10080, 10, true)}>Week 10m</button>
|
||||
<button on:click={() => loadGraph(43200, 60, true)}>Month 1h</button>
|
||||
<button on:click={() => loadGraph(131400, 1440, false)}>Quarter 1d</button>
|
||||
<button on:click={() => loadGraph(262800, 1440, false)}>Half-year 1d</button>
|
||||
<button on:click={() => loadGraph(525600, 1440, false)}>Year 1d</button>
|
||||
<button on:click={() => loadGraph(1051200, 1440, false)}>Two Years 1d</button>
|
||||
<button on:click={() => loadGraph(2628000, 1440, false)}>Five Years 1d</button>
|
||||
<button onclick={() => loadGraph(1440, 1, true)}>Day 1m</button>
|
||||
<button onclick={() => loadGraph(10080, 10, true)}>Week 10m</button>
|
||||
<button onclick={() => loadGraph(43200, 60, true)}>Month 1h</button>
|
||||
<button onclick={() => loadGraph(131400, 1440, false)}>Quarter 1d</button>
|
||||
<button onclick={() => loadGraph(262800, 1440, false)}>Half-year 1d</button>
|
||||
<button onclick={() => loadGraph(525600, 1440, false)}>Year 1d</button>
|
||||
<button onclick={() => loadGraph(1051200, 1440, false)}>Two Years 1d</button>
|
||||
<button onclick={() => loadGraph(2628000, 1440, false)}>Five Years 1d</button>
|
||||
</div>
|
||||
<Chart bind:this={graphBandwidth} data_type="bytes" />
|
||||
<Chart bind:this={graphViews} data_type="number" />
|
||||
@@ -306,22 +306,22 @@ onDestroy(() => {
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<button on:click={() => { getStats('query_name') }}>
|
||||
<button onclick={() => { getStats('query_name') }}>
|
||||
Query
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('calls') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('calls') }}>
|
||||
Calls
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('average_duration') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('average_duration') }}>
|
||||
Avg
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('total_duration') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('total_duration') }}>
|
||||
Total
|
||||
</button>
|
||||
</td>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { preventDefault, stopPropagation } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
@@ -15,13 +16,13 @@ const abuse_types = [
|
||||
"revenge_porn",
|
||||
]
|
||||
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
let rows = $state([])
|
||||
let total_offences = $state(0)
|
||||
|
||||
let expanded = false
|
||||
let creating = false
|
||||
let new_ban_address
|
||||
let new_ban_reason = abuse_types[0]
|
||||
let expanded = $state(false)
|
||||
let creating = $state(false)
|
||||
let new_ban_address = $state()
|
||||
let new_ban_reason = $state(abuse_types[0])
|
||||
|
||||
const get_bans = async () => {
|
||||
loading_start()
|
||||
@@ -103,20 +104,20 @@ onMount(get_bans);
|
||||
Offences {total_offences}
|
||||
</div>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button class:button_highlight={expanded} on:click={() => {expanded = !expanded}}>
|
||||
<button class:button_highlight={expanded} onclick={() => {expanded = !expanded}}>
|
||||
{#if expanded}
|
||||
<i class="icon">unfold_less</i> Collapse all
|
||||
{:else}
|
||||
<i class="icon">unfold_more</i> Expand all
|
||||
{/if}
|
||||
</button>
|
||||
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||
<button class:button_highlight={creating} onclick={() => {creating = !creating}}>
|
||||
<i class="icon">create</i> Add IP ban
|
||||
</button>
|
||||
</div>
|
||||
{#if creating}
|
||||
<div class="highlight_shaded">
|
||||
<form on:submit|preventDefault={create_ban}>
|
||||
<form onsubmit={preventDefault(create_ban)}>
|
||||
<div class="form">
|
||||
<label for="field_address">IP address</label>
|
||||
<input id="field_address" type="text" bind:this={new_ban_address}/>
|
||||
@@ -138,7 +139,8 @@ onMount(get_bans);
|
||||
|
||||
{#each rows as row (row.address)}
|
||||
<Expandable expanded={expanded} click_expand>
|
||||
<div slot="header" class="header">
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.address}</div>
|
||||
<div class="stats">
|
||||
Offences<br/>
|
||||
@@ -148,10 +150,11 @@ onMount(get_bans);
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button on:click|stopPropagation={() => {delete_ban(row.address)}} class="button button_red" style="align-self: center;">
|
||||
<button onclick={stopPropagation(() => {delete_ban(row.address)})} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div class="table_scroll">
|
||||
<table>
|
||||
<thead>
|
||||
|
@@ -7,10 +7,10 @@ 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 invoices: Invoice[] = []
|
||||
let invoices: Invoice[] = $state([])
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
let year = $state(0)
|
||||
let month = $state(0)
|
||||
|
||||
type Total = {
|
||||
count: number
|
||||
@@ -18,8 +18,8 @@ type Total = {
|
||||
vat: number
|
||||
fee: number
|
||||
}
|
||||
let totals_provider: { [id: string]: Total } = {}
|
||||
let totals_country: { [id: string]: Total } = {}
|
||||
let totals_provider: { [id: string]: Total } = $state({})
|
||||
let totals_country: { [id: string]: Total } = $state({})
|
||||
const add_total = (i: Invoice) => {
|
||||
if (totals_provider[i.payment_method] === undefined) {
|
||||
totals_provider[i.payment_method] = {count: 0, amount: 0, vat: 0, fee: 0}
|
||||
@@ -119,14 +119,14 @@ onMount(() => {
|
||||
get_invoices()
|
||||
})
|
||||
|
||||
let status_filter = {
|
||||
let status_filter = $state({
|
||||
canceled: {checked: false},
|
||||
expired: {checked: false},
|
||||
open: {checked: false},
|
||||
paid: {checked: true},
|
||||
}
|
||||
let gateway_filter = {}
|
||||
let method_filter = {}
|
||||
})
|
||||
let gateway_filter = $state({})
|
||||
let method_filter = $state({})
|
||||
|
||||
const filter_invoices = () => {
|
||||
records_hidden = 0
|
||||
@@ -153,26 +153,28 @@ const filter_invoices = () => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
let records_hidden = 0
|
||||
let invoices_filtered: Invoice[] = []
|
||||
let records_hidden = $state(0)
|
||||
let invoices_filtered: Invoice[] = $state([])
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h3>{year + "-" + ("00"+(month)).slice(-2)}</h3>
|
||||
<div class="toolbar">
|
||||
<button on:click={last_month}>
|
||||
<button onclick={last_month}>
|
||||
<i class="icon">chevron_left</i>
|
||||
Previous month
|
||||
</button>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={next_month}>
|
||||
<button onclick={next_month}>
|
||||
Next month
|
||||
<i class="icon">chevron_right</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">Per payment processor</div>
|
||||
{#snippet header()}
|
||||
<div class="header">Per payment processor</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list(totals_provider)}
|
||||
@@ -188,7 +190,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">Per country</div>
|
||||
{#snippet header()}
|
||||
<div class="header">Per country</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list(totals_country)}
|
||||
@@ -204,7 +208,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">In European Union</div>
|
||||
{#snippet header()}
|
||||
<div class="header">In European Union</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list_eu(totals_country)}
|
||||
@@ -220,7 +226,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">PayPal VAT</div>
|
||||
{#snippet header()}
|
||||
<div class="header">PayPal VAT</div>
|
||||
{/snippet}
|
||||
<PayPalVat invoices={invoices}/>
|
||||
</Expandable>
|
||||
|
||||
@@ -233,7 +241,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="status_{filter}"
|
||||
bind:checked={status_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="status_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
@@ -245,7 +253,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="gateway_{filter}"
|
||||
bind:checked={gateway_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="gateway_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
@@ -257,7 +265,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="method_{filter}"
|
||||
bind:checked={method_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="method_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import { mollie_proxy_call } from "./MollieAPI";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { loading_start } from "lib/Loading";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let settlement = {}
|
||||
let payments = []
|
||||
let { settlement = {} } = $props();
|
||||
let payments = $state([])
|
||||
|
||||
let per_country = {}
|
||||
let totals = {
|
||||
let per_country = $state({})
|
||||
let totals = $state({
|
||||
count: 0,
|
||||
vat: 0,
|
||||
amount: 0,
|
||||
}
|
||||
})
|
||||
|
||||
const load_all_payments = async (settlement_id) => {
|
||||
let payments = []
|
||||
|
@@ -8,7 +8,7 @@ import { mollie_proxy_call } from "./MollieAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let response = {}
|
||||
let settlements = []
|
||||
let settlements = $state([])
|
||||
|
||||
const get_settlements = async () => {
|
||||
loading_start()
|
||||
@@ -32,7 +32,8 @@ onMount(get_settlements);
|
||||
<section>
|
||||
{#each settlements as row (row.id)}
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
@@ -47,6 +48,7 @@ onMount(get_settlements);
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<MollieSettlement settlement={row}/>
|
||||
</Expandable>
|
||||
|
@@ -6,15 +6,15 @@ import Euro from "util/Euro.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let response = {}
|
||||
let payments = []
|
||||
let payments = $state([])
|
||||
|
||||
let per_country = {}
|
||||
let totals = {}
|
||||
let per_country = $state({})
|
||||
let totals = $state({})
|
||||
|
||||
let datePicker
|
||||
let rangeMonths = 1
|
||||
let startDate = 0
|
||||
let endDate = 0
|
||||
let datePicker = $state()
|
||||
let rangeMonths = $state(1)
|
||||
let startDate = $state(0)
|
||||
let endDate = $state(0)
|
||||
|
||||
const get_payments = async () => {
|
||||
if (!datePicker.valueAsDate) {
|
||||
@@ -125,7 +125,7 @@ onMount(() => {
|
||||
<input type="date" bind:this={datePicker}/>
|
||||
<div>Months</div>
|
||||
<input type="number" bind:value={rangeMonths}/>
|
||||
<button on:click={get_payments}>Go</button>
|
||||
<button onclick={get_payments}>Go</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -227,7 +227,8 @@ onMount(() => {
|
||||
<h2>Payments</h2>
|
||||
{#each payments as row (row.id)}
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
@@ -250,6 +251,7 @@ onMount(() => {
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div>
|
||||
Amount: <Euro amount={row.metadata.amount} /><br/>
|
||||
VAT: <Euro amount={row.metadata.vat} /><br/>
|
||||
|
@@ -78,6 +78,7 @@ const update_countries = (invoices: Invoice[]) => {
|
||||
{#if per_country["NL"] && totals}
|
||||
<h2>Summary</h2>
|
||||
<table style="width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total PayPal earnings -fees</td>
|
||||
<td><Euro amount={totals.vat+totals.amount-totals.fee}/></td>
|
||||
@@ -90,6 +91,7 @@ const update_countries = (invoices: Invoice[]) => {
|
||||
<td>Total VAT OSS</td>
|
||||
<td><Euro amount={totals.vat-per_country["NL"].vat}/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Accounting information</h2>
|
||||
|
@@ -1,25 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
import { flip } from "svelte/animate";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
|
||||
export let peers = [];
|
||||
$: update_peers(peers)
|
||||
let { peers = $bindable([]) } = $props();
|
||||
let update_peers = (peers) => {
|
||||
for (let peer of peers) {
|
||||
peer.avg_network_total = peer.avg_network_tx + peer.avg_network_rx
|
||||
peer.usage_percent = (peer.avg_network_tx / peer.port_speed) * 100
|
||||
peer.network_ratio = Math.max(peer.avg_network_tx, peer.avg_network_rx) / Math.min(peer.avg_network_tx, peer.avg_network_rx)
|
||||
if (peer.network_ratio === NaN) {
|
||||
peer.network_ratio = 1
|
||||
}
|
||||
}
|
||||
|
||||
sort("")
|
||||
}
|
||||
|
||||
let sort_field = "hostname"
|
||||
let asc = true
|
||||
let sort_field = $state("hostname")
|
||||
let asc = $state(true)
|
||||
let sort = (field) => {
|
||||
if (field !== "" && field === sort_field) {
|
||||
asc = !asc
|
||||
@@ -49,6 +46,9 @@ let sort = (field) => {
|
||||
})
|
||||
peers = peers
|
||||
}
|
||||
run(() => {
|
||||
update_peers(peers)
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="table_scroll">
|
||||
|
@@ -1,35 +1,40 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { formatDuration } from "util/Formatting";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let running_since = ""
|
||||
interface Props {
|
||||
running_since?: string;
|
||||
}
|
||||
|
||||
$: profile_running = running_since != "0001-01-01T00:00:00Z" && running_since != ""
|
||||
let { running_since = "" }: Props = $props();
|
||||
|
||||
let profile_running = $derived(running_since != "0001-01-01T00:00:00Z" && running_since != "")
|
||||
|
||||
const start = async () => {
|
||||
if (!profile_running) {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/admin/cpu_profile",
|
||||
get_endpoint()+"/admin/cpu_profile",
|
||||
{ method: "POST" }
|
||||
);
|
||||
if(resp.status >= 400) {
|
||||
throw new Error(await resp.text());
|
||||
}
|
||||
} else {
|
||||
window.open(window.api_endpoint+"/admin/cpu_profile")
|
||||
window.open(get_endpoint()+"/admin/cpu_profile")
|
||||
}
|
||||
|
||||
dispatch("refresh")
|
||||
}
|
||||
|
||||
let interval
|
||||
let running_time = "0s"
|
||||
let interval: number
|
||||
let running_time = $state("0s")
|
||||
onMount(() => {
|
||||
interval = setInterval(() => {
|
||||
if (profile_running) {
|
||||
running_time = formatDuration(
|
||||
(new Date()).getTime() - Date.parse(running_since),
|
||||
(new Date()).getTime() - Date.parse(running_since), 3
|
||||
)
|
||||
}
|
||||
}, 1000)
|
||||
@@ -43,7 +48,7 @@ onMount(() => {
|
||||
|
||||
<a class="button" href="/api/admin/call_stack">Call stack</a>
|
||||
<a class="button" href="/api/admin/heap_profile">Heap profile</a>
|
||||
<button on:click={start} class:button_red={profile_running}>
|
||||
<button onclick={start} class:button_red={profile_running}>
|
||||
{#if profile_running}
|
||||
Stop CPU profiling (running for {running_time})
|
||||
{:else}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
|
||||
export let row = {}
|
||||
let { row = {} } = $props();
|
||||
</script>
|
||||
|
||||
<table>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { stopPropagation } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
@@ -8,9 +9,9 @@ import BanDetails from "./BanDetails.svelte";
|
||||
import UserLists from "./UserLists.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
let expanded = false
|
||||
let rows = $state([])
|
||||
let total_offences = $state(0)
|
||||
let expanded = $state(false)
|
||||
|
||||
const get_bans = async () => {
|
||||
loading_start()
|
||||
@@ -113,7 +114,7 @@ onMount(get_bans);
|
||||
Offences {total_offences}
|
||||
</div>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button class:button_highlight={expanded} on:click={() => {expanded = !expanded}}>
|
||||
<button class:button_highlight={expanded} onclick={() => {expanded = !expanded}}>
|
||||
{#if expanded}
|
||||
<i class="icon">unfold_less</i> Collapse all
|
||||
{:else}
|
||||
@@ -124,7 +125,8 @@ onMount(get_bans);
|
||||
|
||||
{#each rows as row (row.user_id)}
|
||||
<Expandable expanded={expanded} click_expand>
|
||||
<div slot="header" class="header">
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
{row.user.username}
|
||||
</div>
|
||||
@@ -140,10 +142,11 @@ onMount(get_bans);
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button on:click|stopPropagation={() => {delete_ban(row.user_id)}} class="button button_red" style="align-self: center;">
|
||||
<button onclick={stopPropagation(() => {delete_ban(row.user_id)})} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="toolbar">
|
||||
<Button click={() => impersonate(row.user_id)} icon="login" label="Impersonate user"/>
|
||||
|
@@ -1,11 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
|
||||
export let user_id = ""
|
||||
let files = []
|
||||
interface Props {
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
let { user_id = "" }: Props = $props();
|
||||
let files = $state([])
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
@@ -13,7 +18,7 @@ export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/files",
|
||||
get_endpoint()+"/user/files",
|
||||
{
|
||||
headers: {
|
||||
"Admin-User-Override": user_id,
|
||||
@@ -34,8 +39,8 @@ export const reload = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let sort_field = "date_upload"
|
||||
let asc = false
|
||||
let sort_field = $state("date_upload")
|
||||
let asc = $state(false)
|
||||
const sort = (field) => {
|
||||
if (field !== "" && field === sort_field) {
|
||||
asc = !asc
|
||||
@@ -84,7 +89,7 @@ const sort = (field) => {
|
||||
{#each files as file (file.id)}
|
||||
<tr>
|
||||
<td style="padding: 0; line-height: 1em;">
|
||||
<img src="{window.api_endpoint+file.thumbnail_href}?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
<img src="{get_endpoint()+file.thumbnail_href}?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="/u/{file.id}" target="_blank">{file.name}</a>
|
||||
|
@@ -1,10 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
export let user_id = ""
|
||||
let lists = []
|
||||
interface Props {
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
let { user_id = "" }: Props = $props();
|
||||
let lists = $state([])
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
@@ -12,7 +17,7 @@ export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/lists",
|
||||
get_endpoint()+"/user/lists",
|
||||
{
|
||||
headers: {
|
||||
"Admin-User-Override": user_id,
|
||||
@@ -47,7 +52,7 @@ export const reload = async () => {
|
||||
{#each lists as list (list.id)}
|
||||
<tr>
|
||||
<td style="padding: 0; line-height: 1em;">
|
||||
<img src="{window.api_endpoint}/list/{list.id}/thumbnail?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
<img src="{get_endpoint()}/list/{list.id}/thumbnail?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="/l/{list.id}" target="_blank">{list.title}</a>
|
||||
|
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { fs_encode_path, node_is_shared } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
@@ -10,7 +13,7 @@ export let nav: FSNavigator
|
||||
<a
|
||||
href={"/d"+fs_encode_path(node.path)}
|
||||
class="breadcrumb button flat"
|
||||
on:click|preventDefault={() => {nav.navigate(node.path, true)}}
|
||||
onclick={preventDefault(() => {nav.navigate(node.path, true)})}
|
||||
>
|
||||
{#if node.abuse_type !== undefined}
|
||||
<i class="icon small">block</i>
|
||||
|
@@ -1,31 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
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 "lib/FilesystemAPI";
|
||||
import { color_by_name } from "util/Util.svelte";
|
||||
import { color_by_name } from "util/Util";
|
||||
import { tick } from "svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let visible = false
|
||||
let {
|
||||
nav,
|
||||
visible = $bindable(false)
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
visible?: boolean;
|
||||
} = $props();
|
||||
|
||||
export const toggle = () => visible = !visible
|
||||
|
||||
$: visibility_change(visible)
|
||||
const visibility_change = visible => {
|
||||
if (visible) {
|
||||
update_chart(nav.base, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
$: direct_url = $nav.base.path ? window.location.origin+fs_path_url($nav.base.path) : ""
|
||||
$: share_url = fs_share_url($nav.path)
|
||||
$: direct_share_url = fs_share_hotlink_url($nav.path)
|
||||
|
||||
let chart
|
||||
let chart_timespan = 0
|
||||
let chart_interval = 0
|
||||
let chart: Chart = $state()
|
||||
let chart_timespan = $state(0)
|
||||
let chart_interval = $state(0)
|
||||
let chart_timespans = [
|
||||
{label: "Day (1m)", span: 1440, interval: 1},
|
||||
{label: "Week (1h)", span: 10080, interval: 60},
|
||||
@@ -36,10 +39,9 @@ let chart_timespans = [
|
||||
{label: "Five Years (1d)", span: 2628000, interval: 1440},
|
||||
]
|
||||
|
||||
let total_downloads = 0
|
||||
let total_transfer = 0
|
||||
let total_downloads = $state(0)
|
||||
let total_transfer = $state(0)
|
||||
|
||||
$: update_chart($nav.base, chart_timespan, chart_interval)
|
||||
let update_chart = async (base: FSNode, timespan: number, interval: number) => {
|
||||
if (chart === undefined) {
|
||||
// Wait for the chart element to render, if it's not rendered already
|
||||
@@ -141,6 +143,15 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
|
||||
console.error("Failed to get time series data:", err)
|
||||
}
|
||||
}
|
||||
run(() => {
|
||||
visibility_change(visible)
|
||||
});
|
||||
let direct_url = $derived($nav.base.path ? window.location.origin+fs_path_url($nav.base.path) : "")
|
||||
let share_url = $derived(fs_share_url($nav.path))
|
||||
let direct_share_url = $derived(fs_share_hotlink_url($nav.path))
|
||||
run(() => {
|
||||
update_chart($nav.base, chart_timespan, chart_interval)
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal bind:visible={visible} title="Details" width={($nav.base.type === "file" ? 1000 : 750) + "px"}>
|
||||
@@ -231,7 +242,7 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
|
||||
<div class="button_bar">
|
||||
{#each chart_timespans as ts}
|
||||
<button
|
||||
on:click={() => update_chart($nav.base, ts.span, ts.interval)}
|
||||
onclick={() => update_chart($nav.base, ts.span, ts.interval)}
|
||||
class:button_highlight={chart_timespan == ts.span}>
|
||||
{ts.label}
|
||||
</button>
|
||||
|
@@ -4,13 +4,15 @@ import { formatDataVolume, formatThousands } from "util/Formatting"
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
let loading = true
|
||||
let downloads = 0
|
||||
let transfer_used = 0
|
||||
let loading = $state(true)
|
||||
let downloads = $state(0)
|
||||
let transfer_used = $state(0)
|
||||
let socket = null
|
||||
let error_msg = ""
|
||||
let error_msg = $state("")
|
||||
|
||||
let connected_to = ""
|
||||
|
||||
@@ -22,9 +24,9 @@ onMount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
let total_directories = 0
|
||||
let total_files = 0
|
||||
let total_file_size = 0
|
||||
let total_directories = $state(0)
|
||||
let total_files = $state(0)
|
||||
let total_file_size = $state(0)
|
||||
|
||||
const update_base = async () => {
|
||||
if (!nav.initialized) {
|
||||
|
@@ -12,14 +12,14 @@ 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_preview: FilePreview
|
||||
let toolbar: Toolbar
|
||||
let upload_widget: FSUploadWidget
|
||||
let details_visible = false
|
||||
let edit_window: EditWindow
|
||||
let edit_visible = false
|
||||
let file_preview: FilePreview = $state()
|
||||
let toolbar: Toolbar = $state()
|
||||
let upload_widget: FSUploadWidget = $state()
|
||||
let details_visible = $state(false)
|
||||
let edit_window: EditWindow = $state()
|
||||
let edit_visible = $state(false)
|
||||
|
||||
const nav = new FSNavigator(true)
|
||||
const nav = $state(new FSNavigator(true))
|
||||
|
||||
onMount(() => {
|
||||
if ((window as any).intial_node !== undefined) {
|
||||
@@ -134,7 +134,7 @@ const keydown = (e: KeyboardEvent) => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
<svelte:window onkeydown={keydown} />
|
||||
|
||||
<div class="filesystem">
|
||||
<Breadcrumbs nav={nav}/>
|
||||
|
@@ -6,23 +6,31 @@ import { formatDataVolume } from "util/Formatting";
|
||||
import { user } from "lib/UserStore";
|
||||
import Dialog from "layout/Dialog.svelte";
|
||||
|
||||
let button: HTMLButtonElement
|
||||
let dialog: Dialog
|
||||
|
||||
export let no_login_label = "Pixeldrain"
|
||||
let button: HTMLButtonElement = $state()
|
||||
let dialog: Dialog = $state()
|
||||
|
||||
let {
|
||||
no_login_label = "Pixeldrain",
|
||||
hide_name = true,
|
||||
hide_logo = false,
|
||||
style = "",
|
||||
embedded = false
|
||||
}: {
|
||||
no_login_label?: string;
|
||||
// Hide the label if the screen is smaller than 800px
|
||||
export let hide_name = true
|
||||
export let hide_logo = false
|
||||
export let style = ""
|
||||
export let embedded = false
|
||||
$: target = embedded ? "_blank" : "_self"
|
||||
hide_name?: boolean;
|
||||
hide_logo?: boolean;
|
||||
style?: string;
|
||||
embedded?: boolean;
|
||||
} = $props();
|
||||
|
||||
let target = $derived(embedded ? "_blank" : "_self")
|
||||
|
||||
const open = () => dialog.open(button.getBoundingClientRect())
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<button bind:this={button} on:click={open} class="button round" title="Menu" style={style}>
|
||||
<button bind:this={button} onclick={open} class="button round" title="Menu" style={style}>
|
||||
{#if !hide_logo}
|
||||
<PixeldrainLogo style="height: 1.6em; width: 1.6em;"/>
|
||||
{/if}
|
||||
|
@@ -1,22 +1,24 @@
|
||||
<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 "lib/FilesystemAPI";
|
||||
import { copy_text } from "util/Util.svelte";
|
||||
import { copy_text } from "util/Util";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import Dialog from "layout/Dialog.svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
let path: FSNode[]
|
||||
let base: FSNode
|
||||
let toast = ""
|
||||
let share_url = ""
|
||||
let direct_share_url = ""
|
||||
let is_parent = false
|
||||
let parent_node: FSNode
|
||||
let base: FSNode = $state()
|
||||
let toast = $state("")
|
||||
let share_url = $state("")
|
||||
let direct_share_url = $state("")
|
||||
let is_parent = $state(false)
|
||||
let parent_node: FSNode = $state()
|
||||
|
||||
let dialog: Dialog
|
||||
let dialog: Dialog = $state()
|
||||
export const open = async (e: MouseEvent, p: FSNode[]) => {
|
||||
path = p
|
||||
base = path[path.length-1]
|
||||
@@ -113,7 +115,7 @@ const share = async () => {
|
||||
<img src={fs_node_icon(parent_node, 64, 64)} class="node_icon" alt="icon"/>
|
||||
{parent_node.name}
|
||||
<br/>
|
||||
<button on:click={async e => {await make_public(); await share()}} style="display: inline;">
|
||||
<button onclick={async e => {await make_public(); await share()}} style="display: inline;">
|
||||
Only share
|
||||
<img src={fs_node_icon(base, 64, 64)} class="node_icon" alt="icon"/>
|
||||
{base.name}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { copy_text } from "util/Util.svelte";
|
||||
import { copy_text } from "util/Util";
|
||||
import FileStats from "./FileStats.svelte";
|
||||
import type { FSNavigator } from "./FSNavigator";
|
||||
import EditWindow from "./edit_window/EditWindow.svelte";
|
||||
@@ -10,12 +10,20 @@ import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bo
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let details_visible = false
|
||||
export let edit_window: EditWindow
|
||||
export let edit_visible = false
|
||||
let share_dialog: ShareDialog
|
||||
let link_copied = false
|
||||
let {
|
||||
nav = $bindable(),
|
||||
details_visible = $bindable(false),
|
||||
edit_window,
|
||||
edit_visible = $bindable(false)
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
details_visible?: boolean;
|
||||
edit_window: EditWindow;
|
||||
edit_visible?: boolean;
|
||||
} = $props();
|
||||
|
||||
let share_dialog: ShareDialog = $state()
|
||||
let link_copied = $state(false)
|
||||
export const copy_link = () => {
|
||||
const share_url = fs_share_url($nav.path)
|
||||
if (share_url === "") {
|
||||
@@ -34,36 +42,36 @@ export const copy_link = () => {
|
||||
<FileStats nav={nav}/>
|
||||
|
||||
<div class="button_row">
|
||||
<button on:click={() => {nav.open_sibling(-1)}}>
|
||||
<button onclick={() => {nav.open_sibling(-1)}}>
|
||||
<i class="icon">skip_previous</i>
|
||||
</button>
|
||||
<button on:click={() => {nav.shuffle = !nav.shuffle}} class:button_highlight={nav.shuffle}>
|
||||
<button onclick={() => {nav.shuffle = !nav.shuffle}} class:button_highlight={nav.shuffle}>
|
||||
<i class="icon">shuffle</i>
|
||||
</button>
|
||||
<button on:click={() => {nav.open_sibling(1)}}>
|
||||
<button onclick={() => {nav.open_sibling(1)}}>
|
||||
<i class="icon">skip_next</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button on:click={() => dispatch("download")}>
|
||||
<button onclick={() => dispatch("download")}>
|
||||
<i class="icon">save</i>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
|
||||
{#if is_bookmark($bookmarks_store, $nav.base.id)}
|
||||
<button on:click={() => bookmark_del($nav.base.id)}>
|
||||
<button onclick={() => bookmark_del($nav.base.id)}>
|
||||
<i class="icon">bookmark_remove</i>
|
||||
<span>Bookmark</span>
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={() => bookmark_add($nav.base)}>
|
||||
<button onclick={() => bookmark_add($nav.base)}>
|
||||
<i class="icon">bookmark_add</i>
|
||||
<span>Bookmark</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if path_is_shared($nav.path)}
|
||||
<button on:click={copy_link} class:button_highlight={link_copied}>
|
||||
<button onclick={copy_link} class:button_highlight={link_copied}>
|
||||
<i class="icon">content_copy</i>
|
||||
<span><u>C</u>opy link</span>
|
||||
</button>
|
||||
@@ -71,19 +79,19 @@ export const copy_link = () => {
|
||||
|
||||
<!-- Share button is enabled when: The browser has a sharing API, or the user can edit the file (to enable sharing)-->
|
||||
{#if navigator.share !== undefined || $nav.permissions.write === true}
|
||||
<button on:click={(e) => share_dialog.open(e, nav.path)}>
|
||||
<button onclick={(e) => share_dialog.open(e, nav.path)}>
|
||||
<i class="icon">share</i>
|
||||
<span>Share</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button on:click={() => details_visible = !details_visible} class:button_highlight={details_visible}>
|
||||
<button onclick={() => details_visible = !details_visible} class:button_highlight={details_visible}>
|
||||
<i class="icon">help</i>
|
||||
<span>Deta<u>i</u>ls</span>
|
||||
</button>
|
||||
|
||||
{#if $nav.base.id !== "me" && $nav.permissions.write === true}
|
||||
<button on:click={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
|
||||
<button onclick={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
|
||||
<i class="icon">edit</i>
|
||||
<span><u>E</u>dit</span>
|
||||
</button>
|
||||
|
@@ -3,10 +3,14 @@ import Button from "layout/Button.svelte";
|
||||
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI";
|
||||
import PermissionButton from "./PermissionButton.svelte";
|
||||
|
||||
export let options: NodeOptions
|
||||
let {
|
||||
options = $bindable()
|
||||
}: {
|
||||
options: NodeOptions;
|
||||
} = $props();
|
||||
|
||||
let new_user_id = ""
|
||||
let new_user_perms = <FSPermissions>{read: true}
|
||||
let new_user_id = $state("")
|
||||
let new_user_perms = $state(<FSPermissions>{read: true})
|
||||
const add_user = (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (options.user_permissions === undefined) {
|
||||
@@ -19,8 +23,8 @@ const del_user = (id: string) => {
|
||||
options.user_permissions = options.user_permissions
|
||||
}
|
||||
|
||||
let new_password = ""
|
||||
let new_password_perms = <FSPermissions>{read: true}
|
||||
let new_password = $state("")
|
||||
let new_password_perms = $state(<FSPermissions>{read: true})
|
||||
const add_password = (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (options.password_permissions === undefined) {
|
||||
@@ -64,7 +68,7 @@ const del_password = (pass: string) => {
|
||||
not receive an e-mail invite. Giving write access to a user without giving
|
||||
read access as well does not actually allow them to write anything.
|
||||
</p>
|
||||
<form on:submit={add_user} class="row">
|
||||
<form onsubmit={add_user} class="row">
|
||||
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
<div class="perms">
|
||||
@@ -94,7 +98,7 @@ const del_password = (pass: string) => {
|
||||
<p>
|
||||
<b>This feature is not implemented currently!</b>
|
||||
</p>
|
||||
<form on:submit={add_password} class="row">
|
||||
<form onsubmit={add_password} class="row">
|
||||
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1">
|
||||
<Button type="submit" icon="add" label="Add"/>
|
||||
<div class="perms">
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
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 "lib/FilesystemAPI";
|
||||
@@ -7,11 +8,16 @@ import HelpButton from "layout/HelpButton.svelte";
|
||||
import FilePicker from "filesystem/filemanager/FilePicker.svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let file: FSNode
|
||||
export let options: NodeOptions
|
||||
export let enabled: boolean
|
||||
let {
|
||||
file = $bindable(),
|
||||
options = $bindable(),
|
||||
enabled = $bindable()
|
||||
}: {
|
||||
file: FSNode;
|
||||
options: NodeOptions;
|
||||
enabled: boolean;
|
||||
} = $props();
|
||||
|
||||
$: update_colors(options)
|
||||
const update_colors = (options: NodeOptions) => {
|
||||
if (enabled) {
|
||||
options.branding_enabled = "true"
|
||||
@@ -21,7 +27,7 @@ const update_colors = (options: NodeOptions) => {
|
||||
}
|
||||
}
|
||||
|
||||
let picker: FilePicker
|
||||
let picker: FilePicker = $state()
|
||||
let picking = ""
|
||||
const pick_image = (type: string) => {
|
||||
picking = type
|
||||
@@ -61,7 +67,10 @@ const handle_picker = async (e: CustomEvent<FSNode[]>) => {
|
||||
}
|
||||
}
|
||||
|
||||
let highlight_info = false
|
||||
let highlight_info = $state(false)
|
||||
run(() => {
|
||||
update_colors(options)
|
||||
});
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
@@ -127,7 +136,7 @@ let highlight_info = false
|
||||
working. Recommended dimensions for the header image are 1000x90 px.
|
||||
</p>
|
||||
<div>Header image ID</div>
|
||||
<button on:click={() => pick_image("brand_header_image")}>
|
||||
<button onclick={() => pick_image("brand_header_image")}>
|
||||
<i class="icon">folder_open</i>
|
||||
Pick
|
||||
</button>
|
||||
@@ -135,7 +144,7 @@ let highlight_info = false
|
||||
<div>Header image link</div>
|
||||
<input class="span2" type="text" bind:value={options.brand_header_link}/>
|
||||
<div>Background image ID</div>
|
||||
<button on:click={() => pick_image("brand_background_image")}>
|
||||
<button onclick={() => pick_image("brand_background_image")}>
|
||||
<i class="icon">folder_open</i>
|
||||
Pick
|
||||
</button>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { fs_rename, fs_update, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
|
||||
import Modal from "util/Modal.svelte";
|
||||
import BrandingOptions from "./BrandingOptions.svelte";
|
||||
@@ -9,13 +10,18 @@ import AccessControl from "./AccessControl.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let file: FSNode = {} as FSNode
|
||||
let options: NodeOptions = {} as NodeOptions
|
||||
let file: FSNode = $state({} as FSNode)
|
||||
let options: NodeOptions = $state({} as NodeOptions)
|
||||
|
||||
let custom_css = ""
|
||||
let custom_css = $state("")
|
||||
|
||||
export let visible: boolean
|
||||
let {
|
||||
nav,
|
||||
visible = $bindable()
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
visible: boolean;
|
||||
} = $props();
|
||||
|
||||
// Open the edit window. Argument 1 is the file to edit, 2 is whether the file
|
||||
// should be opened after the user finishes editing and 3 is the default tab
|
||||
@@ -54,11 +60,11 @@ export const edit = (f: FSNode, oae = false, open_tab = "") => {
|
||||
visible = true
|
||||
}
|
||||
|
||||
let tab = "file"
|
||||
let open_after_edit = false
|
||||
let tab = $state("file")
|
||||
let open_after_edit = $state(false)
|
||||
|
||||
let new_name = ""
|
||||
let branding_enabled = false
|
||||
let new_name = $state("")
|
||||
let branding_enabled = $state(false)
|
||||
|
||||
const save = async (keep_editing = false) => {
|
||||
console.debug("Saving file", file.path)
|
||||
@@ -106,27 +112,27 @@ const save = async (keep_editing = false) => {
|
||||
|
||||
<Modal bind:visible={visible} title="Edit {file.name}" width="800px" form="edit_form" style="color: var(--body_text_color); {custom_css}">
|
||||
<div class="tab_bar">
|
||||
<button class:button_highlight={tab === "file"} on:click={() => tab = "file"}>
|
||||
<button class:button_highlight={tab === "file"} onclick={() => tab = "file"}>
|
||||
<i class="icon">edit</i>
|
||||
Properties
|
||||
</button>
|
||||
<button class:button_highlight={tab === "share"} on:click={() => tab = "share"}>
|
||||
<button class:button_highlight={tab === "share"} onclick={() => tab = "share"}>
|
||||
<i class="icon">share</i>
|
||||
Sharing
|
||||
</button>
|
||||
{#if $nav.permissions.owner}
|
||||
<button class:button_highlight={tab === "access"} on:click={() => tab = "access"}>
|
||||
<button class:button_highlight={tab === "access"} onclick={() => tab = "access"}>
|
||||
<i class="icon">key</i>
|
||||
Access control
|
||||
</button>
|
||||
{/if}
|
||||
<button class:button_highlight={tab === "branding"} on:click={() => tab = "branding"}>
|
||||
<button class:button_highlight={tab === "branding"} onclick={() => tab = "branding"}>
|
||||
<i class="icon">palette</i>
|
||||
Branding
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="edit_form" on:submit|preventDefault={() => save(false)}></form>
|
||||
<form id="edit_form" onsubmit={preventDefault(() => save(false))}></form>
|
||||
|
||||
<div class="tab_content">
|
||||
{#if tab === "file"}
|
||||
@@ -138,7 +144,10 @@ const save = async (keep_editing = false) => {
|
||||
bind:open_after_edit
|
||||
/>
|
||||
{:else if tab === "share"}
|
||||
<SharingOptions bind:file bind:options on:save={() => save(true)} />
|
||||
<SharingOptions
|
||||
bind:file
|
||||
bind:options
|
||||
/>
|
||||
{:else if tab === "access"}
|
||||
<AccessControl bind:options />
|
||||
{:else if tab === "branding"}
|
||||
|
@@ -5,13 +5,21 @@ import PathLink from "filesystem/util/PathLink.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let file: FSNode = {} as FSNode
|
||||
export let new_name: string
|
||||
export let visible: boolean
|
||||
export let open_after_edit: boolean
|
||||
let {
|
||||
nav,
|
||||
file = $bindable({} as FSNode),
|
||||
new_name = $bindable(),
|
||||
visible = $bindable(),
|
||||
open_after_edit = $bindable(false)
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
file?: FSNode;
|
||||
new_name: string;
|
||||
visible: boolean;
|
||||
open_after_edit: boolean;
|
||||
} = $props();
|
||||
|
||||
$: is_root_dir = file.path === "/"+file.id
|
||||
let is_root_dir = $derived(file.path === "/"+file.id)
|
||||
|
||||
const delete_file = async (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
@@ -2,9 +2,15 @@
|
||||
import ToggleButton from "layout/ToggleButton.svelte";
|
||||
import type { FSPermissions } from "lib/FilesystemAPI";
|
||||
|
||||
export let permissions = <FSPermissions>{}
|
||||
let {
|
||||
permissions = $bindable()
|
||||
}: {
|
||||
permissions: FSPermissions
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
{#if permissions !== undefined}
|
||||
<ToggleButton group_first bind:on={permissions.read}>Read</ToggleButton>
|
||||
<ToggleButton group_middle bind:on={permissions.write}>Write</ToggleButton>
|
||||
<ToggleButton group_last bind:on={permissions.delete}>Delete</ToggleButton>
|
||||
{/if}
|
||||
|
@@ -1,18 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { domain_url } from "util/Util.svelte";
|
||||
import { run } from 'svelte/legacy';
|
||||
import { domain_url } from "util/Util";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import { node_is_shared, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
|
||||
import AccessControl from "./AccessControl.svelte";
|
||||
|
||||
export let file: FSNode = {} as FSNode
|
||||
export let options: NodeOptions
|
||||
let {
|
||||
file = $bindable(),
|
||||
options = $bindable(),
|
||||
}: {
|
||||
file?: FSNode;
|
||||
options: NodeOptions;
|
||||
} = $props();
|
||||
|
||||
let embed_html: string
|
||||
let preview_area: HTMLDivElement
|
||||
let embed_html: string = $state()
|
||||
let preview_area: HTMLDivElement = $state()
|
||||
|
||||
$: share_link = window.location.protocol+"//"+window.location.host+"/d/"+file.id
|
||||
$: embed_iframe(file, options)
|
||||
const embed_iframe = (file: FSNode, options: NodeOptions) => {
|
||||
if (!node_is_shared(file)) {
|
||||
example = false
|
||||
@@ -28,7 +32,7 @@ const embed_iframe = (file: FSNode, options: NodeOptions) => {
|
||||
`></iframe>`
|
||||
}
|
||||
|
||||
let example = false
|
||||
let example = $state(false)
|
||||
const toggle_example = () => {
|
||||
if (node_is_shared(file)) {
|
||||
example = !example
|
||||
@@ -40,6 +44,10 @@ const toggle_example = () => {
|
||||
}
|
||||
}
|
||||
|
||||
let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id)
|
||||
run(() => {
|
||||
embed_iframe(file, options)
|
||||
});
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
@@ -78,7 +86,7 @@ const toggle_example = () => {
|
||||
<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={!node_is_shared(file)}>
|
||||
<button onclick={toggle_example} class:button_highlight={example} disabled={!node_is_shared(file)}>
|
||||
<i class="icon">visibility</i> Show example
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -1,9 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { FSNodeProperties } from "lib/FilesystemAPI";
|
||||
|
||||
export let properties: FSNodeProperties = {} as FSNodeProperties
|
||||
let {
|
||||
properties = $bindable({} as FSNodeProperties)
|
||||
}: {
|
||||
properties?: FSNodeProperties;
|
||||
} = $props();
|
||||
|
||||
let current_theme = -1
|
||||
let current_theme = $state(-1)
|
||||
|
||||
const set_theme = (index: number) => {
|
||||
current_theme = index
|
||||
@@ -71,7 +75,7 @@ const themes = [
|
||||
</script>
|
||||
|
||||
{#each themes as theme, index (theme.name)}
|
||||
<button class:button_highlight={current_theme === index} on:click={() => {set_theme(index)}}>
|
||||
<button class:button_highlight={current_theme === index} onclick={() => {set_theme(index)}}>
|
||||
{theme.name}
|
||||
</button>
|
||||
{/each}
|
||||
|
@@ -6,18 +6,25 @@ import { FileAction } from "./FileManagerLib";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let show_hidden = false
|
||||
export let large_icons = false
|
||||
export let hide_edit = false
|
||||
let {
|
||||
nav,
|
||||
show_hidden = false,
|
||||
large_icons = false,
|
||||
hide_edit = false
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
show_hidden?: boolean;
|
||||
large_icons?: boolean;
|
||||
hide_edit?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="directory">
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
class="node"
|
||||
class:node_selected={child.fm_selected}
|
||||
class:hidden={child.name.startsWith(".") && !show_hidden}
|
||||
@@ -26,20 +33,15 @@ export let hide_edit = false
|
||||
<div class="node_name">
|
||||
{child.name}
|
||||
</div>
|
||||
|
||||
{#if node_is_shared(child)}
|
||||
<a
|
||||
href="/d/{child.id}"
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
|
||||
class="button flat action_button"
|
||||
>
|
||||
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if !hide_edit}
|
||||
<button
|
||||
class="action_button flat"
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
|
||||
onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
|
||||
>
|
||||
<i class="icon">menu</i>
|
||||
</button>
|
||||
|
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
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
|
||||
let { nav }: { nav: FSNavigator } = $props();
|
||||
|
||||
let name_input: HTMLInputElement;
|
||||
let new_dir_name = ""
|
||||
let error_msg = ""
|
||||
let name_input: HTMLInputElement = $state();
|
||||
let new_dir_name = $state("")
|
||||
let error_msg = $state("")
|
||||
let create_dir = async () => {
|
||||
let form = new FormData()
|
||||
form.append("type", "dir")
|
||||
@@ -42,7 +43,7 @@ onMount(() => {
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form id="create_dir_form" class="create_dir" on:submit|preventDefault={create_dir}>
|
||||
<form id="create_dir_form" class="create_dir" onsubmit={preventDefault(create_dir)}>
|
||||
<img src="/res/img/mime/folder.png" class="icon" alt="icon"/>
|
||||
<input class="dirname" type="text" bind:this={name_input} bind:value={new_dir_name} />
|
||||
<Button form="create_dir_form" type="submit" icon="create_new_folder" label="Create"/>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "lib/FilesystemAPI"
|
||||
import { onMount } from "svelte"
|
||||
import CreateDirectory from "./CreateDirectory.svelte"
|
||||
@@ -16,19 +17,29 @@ import { FileAction, type FileEvent } from "./FileManagerLib";
|
||||
import FileMenu from "./FileMenu.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let upload_widget: FsUploadWidget
|
||||
export let edit_window: EditWindow
|
||||
export let directory_view = ""
|
||||
let large_icons = false
|
||||
let {
|
||||
nav = $bindable(),
|
||||
upload_widget,
|
||||
edit_window = $bindable(),
|
||||
directory_view = $bindable(""),
|
||||
children
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
upload_widget: FsUploadWidget;
|
||||
edit_window: EditWindow;
|
||||
directory_view?: string;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let large_icons = $state(false)
|
||||
let uploader: FsUploadWidget
|
||||
let mode = "viewing"
|
||||
let creating_dir = false
|
||||
let show_hidden = false
|
||||
let file_menu: FileMenu
|
||||
let mode = $state("viewing")
|
||||
let creating_dir = $state(false)
|
||||
let show_hidden = $state(false)
|
||||
let file_menu: FileMenu = $state()
|
||||
|
||||
export const upload = (files: File[]) => {
|
||||
return uploader.upload(files)
|
||||
return uploader.upload_files(files)
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
@@ -228,10 +239,6 @@ const select_node = (index: number) => {
|
||||
last_selected_node = index
|
||||
}
|
||||
|
||||
// When the directory is reloaded we want to keep our selection, so this
|
||||
// function watches the children array for changes and updates the selection
|
||||
// when it changes
|
||||
$: update($nav.children)
|
||||
const update = (children: FSNode[]) => {
|
||||
creating_dir = false
|
||||
|
||||
@@ -245,8 +252,8 @@ const update = (children: FSNode[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
let moving_files = 0
|
||||
let moving_directories = 0
|
||||
let moving_files = $state(0)
|
||||
let moving_directories = $state(0)
|
||||
const move_start = () => {
|
||||
moving_files = 0
|
||||
moving_directories = 0
|
||||
@@ -296,9 +303,15 @@ onMount(() => {
|
||||
directory_view = "list"
|
||||
}
|
||||
})
|
||||
// When the directory is reloaded we want to keep our selection, so this
|
||||
// function watches the children array for changes and updates the selection
|
||||
// when it changes
|
||||
run(() => {
|
||||
update($nav.children)
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keypress} on:keyup={keypress} />
|
||||
<svelte:window onkeydown={keypress} onkeyup={keypress} />
|
||||
|
||||
<div
|
||||
class="container"
|
||||
@@ -311,25 +324,25 @@ onMount(() => {
|
||||
{#if mode === "viewing"}
|
||||
<div class="toolbar">
|
||||
<div class="toolbar_left">
|
||||
<button on:click={navigate_back} title="Back">
|
||||
<button onclick={navigate_back} title="Back">
|
||||
<i class="icon">arrow_back</i>
|
||||
</button>
|
||||
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<button onclick={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<i class="icon">north</i>
|
||||
</button>
|
||||
<button on:click={() => nav.reload()} title="Refresh directory listing">
|
||||
<button onclick={() => nav.reload()} title="Refresh directory listing">
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar_middle">
|
||||
<button on:click={() => toggle_view()} title="Switch between gallery, list and compact view">
|
||||
<button onclick={() => toggle_view()} title="Switch between gallery, list and compact view">
|
||||
<i class="icon" class:button_highlight={directory_view === "list"}>list</i>
|
||||
<i class="icon" class:button_highlight={directory_view === "gallery"}>collections</i>
|
||||
<i class="icon" class:button_highlight={directory_view === "compact"}>view_compact</i>
|
||||
</button>
|
||||
|
||||
<button class="button_large_icons" on:click={() => toggle_large_icons()} title="Switch between large and small icons">
|
||||
<button class="button_large_icons" onclick={() => toggle_large_icons()} title="Switch between large and small icons">
|
||||
{#if large_icons}
|
||||
<i class="icon">zoom_out</i>
|
||||
{:else}
|
||||
@@ -337,7 +350,7 @@ onMount(() => {
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button on:click={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
|
||||
<button onclick={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
|
||||
{#if show_hidden}
|
||||
<i class="icon">visibility_off</i>
|
||||
{:else}
|
||||
@@ -348,13 +361,13 @@ onMount(() => {
|
||||
|
||||
<div class="toolbar_right">
|
||||
{#if $nav.permissions.write}
|
||||
<button on:click={() => upload_widget.pick_files()} title="Upload files to this directory">
|
||||
<button onclick={() => upload_widget.pick_files()} title="Upload files to this directory">
|
||||
<i class="icon">cloud_upload</i>
|
||||
</button>
|
||||
|
||||
<Button click={() => {creating_dir = !creating_dir}} highlight={creating_dir} icon="create_new_folder" title="Make folder"/>
|
||||
|
||||
<button on:click={selecting_mode} title="Select and delete files">
|
||||
<button onclick={selecting_mode} title="Select and delete files">
|
||||
<i class="icon">select_all</i>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -368,7 +381,7 @@ onMount(() => {
|
||||
<Button click={viewing_mode} icon="close"/>
|
||||
<div class="toolbar_spacer">Selecting files</div>
|
||||
<Button click={move_start} icon="drive_file_move" label="Move"/>
|
||||
<button on:click={delete_selected} class="button_red">
|
||||
<button onclick={delete_selected} class="button_red">
|
||||
<i class="icon">delete</i>
|
||||
Delete
|
||||
</button>
|
||||
@@ -407,7 +420,7 @@ onMount(() => {
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
{#if directory_view === "list"}
|
||||
<ListView nav={nav} show_hidden={show_hidden} large_icons={large_icons} on:file={file_event} />
|
||||
@@ -418,7 +431,7 @@ onMount(() => {
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<FileMenu bind:this={file_menu} bind:nav bind:edit_window />
|
||||
<FileMenu bind:this={file_menu} nav={nav} edit_window={edit_window} />
|
||||
|
||||
<style>
|
||||
.container {
|
||||
|
@@ -7,10 +7,16 @@ import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bo
|
||||
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 = null
|
||||
let {
|
||||
nav,
|
||||
edit_window
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
edit_window: EditWindow;
|
||||
} = $props();
|
||||
|
||||
let dialog: Dialog = $state()
|
||||
let node: FSNode = $state(null)
|
||||
|
||||
export const open = async (n: FSNode, target: EventTarget) => {
|
||||
node = n
|
||||
|
@@ -9,25 +9,28 @@ import { FSNavigator } from "filesystem/FSNavigator";
|
||||
import type { FSNode } from "lib/FilesystemAPI";
|
||||
import { FileAction, type FileEvent } from "./FileManagerLib";
|
||||
|
||||
let nav = new FSNavigator(false)
|
||||
let modal: Modal
|
||||
let nav = $state(new FSNavigator(false))
|
||||
let modal: Modal = $state()
|
||||
let dispatch = createEventDispatcher()
|
||||
let directory_view = ""
|
||||
let large_icons = false
|
||||
let show_hidden = false
|
||||
export let select_multiple = false
|
||||
let directory_view = $state("")
|
||||
let large_icons = $state(false)
|
||||
let show_hidden = $state(false)
|
||||
|
||||
let { select_multiple = false }: {
|
||||
select_multiple?: boolean;
|
||||
} = $props();
|
||||
|
||||
export const open = (path: string) => {
|
||||
modal.show()
|
||||
nav.navigate(path, false)
|
||||
}
|
||||
|
||||
$: selected_files = $nav.children.reduce((acc, file) => {
|
||||
let selected_files = $derived($nav.children.reduce((acc, file) => {
|
||||
if (file.fm_selected) {
|
||||
acc++
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
}, 0))
|
||||
|
||||
// Navigation functions
|
||||
|
||||
@@ -128,17 +131,18 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={detect_shift} on:keyup={detect_shift} />
|
||||
<svelte:window onkeydown={detect_shift} onkeyup={detect_shift} />
|
||||
|
||||
<Modal bind:this={modal} width="900px">
|
||||
<div class="header" slot="title">
|
||||
<button class="button round" on:click={modal.hide}>
|
||||
{#snippet title()}
|
||||
<div class="header" >
|
||||
<button class="button round" onclick={modal.hide}>
|
||||
<i class="icon">close</i>
|
||||
</button>
|
||||
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<button onclick={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<i class="icon">north</i>
|
||||
</button>
|
||||
<button on:click={() => nav.reload()} title="Refresh directory listing">
|
||||
<button onclick={() => nav.reload()} title="Refresh directory listing">
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
|
||||
@@ -146,7 +150,7 @@ onMount(() => {
|
||||
Selected {selected_files} files
|
||||
</div>
|
||||
|
||||
<button on:click={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
|
||||
<button onclick={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
|
||||
{#if show_hidden}
|
||||
<i class="icon">visibility_off</i>
|
||||
{:else}
|
||||
@@ -154,16 +158,17 @@ onMount(() => {
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button on:click={() => toggle_view()} title="Switch between gallery, list and compact view">
|
||||
<button onclick={() => toggle_view()} title="Switch between gallery, list and compact view">
|
||||
<i class="icon" class:button_highlight={directory_view === "list"}>list</i>
|
||||
<i class="icon" class:button_highlight={directory_view === "gallery"}>collections</i>
|
||||
<i class="icon" class:button_highlight={directory_view === "compact"}>view_compact</i>
|
||||
</button>
|
||||
|
||||
<button class="button button_highlight round" on:click={done}>
|
||||
<button class="button button_highlight round" onclick={done}>
|
||||
<i class="icon">done</i> Pick
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<Breadcrumbs nav={nav}/>
|
||||
|
||||
|
@@ -5,17 +5,23 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import { FileAction } from "./FileManagerLib";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let show_hidden = false
|
||||
export let large_icons = false
|
||||
let {
|
||||
nav,
|
||||
show_hidden = false,
|
||||
large_icons = false
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
show_hidden?: boolean;
|
||||
large_icons?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="gallery">
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
<a class="file"
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
class:selected={child.fm_selected}
|
||||
class:hidden={child.name.startsWith(".") && !show_hidden}
|
||||
class:large_icons
|
||||
|
@@ -8,25 +8,35 @@ import { FileAction } from "./FileManagerLib";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let show_hidden = false
|
||||
export let large_icons = false
|
||||
export let hide_edit = false
|
||||
export let hide_branding = false
|
||||
let {
|
||||
nav,
|
||||
show_hidden = false,
|
||||
large_icons = false,
|
||||
hide_edit = false,
|
||||
hide_branding = false
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
show_hidden?: boolean;
|
||||
large_icons?: boolean;
|
||||
hide_edit?: boolean;
|
||||
hide_branding?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="directory">
|
||||
<table class="directory">
|
||||
<thead>
|
||||
<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 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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
<tr
|
||||
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
|
||||
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
|
||||
class="node"
|
||||
class:node_selected={child.fm_selected}
|
||||
class:hidden={child.name.startsWith(".") && !show_hidden}
|
||||
@@ -35,7 +45,9 @@ export let hide_branding = false
|
||||
<img src={fs_node_icon(child, 64, 64)} class="node_icon" class:large_icons alt="icon"/>
|
||||
</td>
|
||||
<td class="node_name">
|
||||
<a href={"/d"+fs_encode_path(child.path)}>
|
||||
{child.name}
|
||||
</a>
|
||||
</td>
|
||||
<td class="node_size hide_small">
|
||||
{#if child.type === "file"}
|
||||
@@ -49,27 +61,28 @@ export let hide_branding = false
|
||||
{:else if node_is_shared(child)}
|
||||
<a
|
||||
href="/d/{child.id}"
|
||||
on:click={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
|
||||
onclick={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
|
||||
class="button action_button"
|
||||
>
|
||||
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
|
||||
</a>
|
||||
{/if}
|
||||
{#if child.properties !== undefined && child.properties.branding_enabled === "true" && !hide_branding}
|
||||
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Branding, original: e})}>
|
||||
<button class="action_button" onclick={e => dispatch("file", {index: index, action: FileAction.Branding, original: e})}>
|
||||
<i class="icon">palette</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if !hide_edit}
|
||||
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}>
|
||||
<button class="action_button" onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}>
|
||||
<i class="icon">menu</i>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</a>
|
||||
</tr>
|
||||
{/each}
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<style>
|
||||
.directory {
|
||||
|
@@ -1,19 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
|
||||
import { onMount } from "svelte";
|
||||
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
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
let search_bar: HTMLInputElement
|
||||
let error = ""
|
||||
let search_term = ""
|
||||
let search_results: string[] = []
|
||||
let selected_result = 0
|
||||
let search_bar: HTMLInputElement = $state()
|
||||
let error = $state("")
|
||||
let search_term = $state("")
|
||||
let search_results: string[] = $state([])
|
||||
let selected_result = $state(0)
|
||||
let searching = false
|
||||
let last_searched_term = ""
|
||||
let last_limit = 10
|
||||
let last_limit = $state(10)
|
||||
|
||||
onMount(() => {
|
||||
// Clear results when the user moves to a new directory
|
||||
@@ -144,7 +148,7 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={window_keydown} />
|
||||
<svelte:window onkeydown={window_keydown} />
|
||||
|
||||
{#if error === "path_not_found" || error === "node_is_a_directory"}
|
||||
<div class="highlight_yellow center">
|
||||
@@ -160,7 +164,7 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
{/if}
|
||||
|
||||
<div class="center">
|
||||
<form class="search_form" on:submit|preventDefault={submit_search}>
|
||||
<form class="search_form" onsubmit={preventDefault(submit_search)}>
|
||||
<i class="icon">search</i>
|
||||
<input
|
||||
bind:this={search_bar}
|
||||
@@ -169,12 +173,12 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
placeholder="Press / to search in {$nav.base.name}"
|
||||
style="width: 100%;"
|
||||
bind:value={search_term}
|
||||
on:keydown={input_keydown}
|
||||
on:keyup={input_keyup}
|
||||
onkeydown={input_keydown}
|
||||
onkeyup={input_keyup}
|
||||
/>
|
||||
{#if search_term !== ""}
|
||||
<!-- Button needs to be of button type in order to not submit the form -->
|
||||
<button on:click={() => clear_search(false)} type="button">
|
||||
<button onclick={() => clear_search(false)} type="button">
|
||||
<i class="icon">close</i>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -188,7 +192,7 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
{#each search_results as result, index}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(result)}
|
||||
on:click|preventDefault={() => open_result(index)}
|
||||
onclick={preventDefault(() => open_result(index))}
|
||||
class="node"
|
||||
class:node_selected={selected_result === index}
|
||||
>
|
||||
@@ -203,7 +207,7 @@ const window_keydown = (e: KeyboardEvent) => {
|
||||
{#if search_results.length === last_limit}
|
||||
<div class="node">
|
||||
<div class="node_name" style="text-align: center;">
|
||||
<button on:click={() => {search(last_limit + 100)}}>
|
||||
<button onclick={() => {search(last_limit + 100)}}>
|
||||
<i class="icon">expand_more</i>
|
||||
More results
|
||||
</button>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
export type UploadJob = {
|
||||
task_id: number,
|
||||
file: File,
|
||||
@@ -14,9 +14,11 @@ import { tick } from "svelte";
|
||||
import UploadProgress from "./UploadProgress.svelte";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
let file_input_field: HTMLInputElement
|
||||
let file_input_field: HTMLInputElement = $state()
|
||||
let file_input_change = (e: Event) => {
|
||||
// Start uploading the files async
|
||||
upload_files((e.target as HTMLInputElement).files)
|
||||
@@ -28,8 +30,8 @@ export const pick_files = () => {
|
||||
file_input_field.click()
|
||||
}
|
||||
|
||||
let visible = false
|
||||
let upload_queue: UploadJob[] = [];
|
||||
let visible = $state(false)
|
||||
let upload_queue: UploadJob[] = $state([]);
|
||||
let task_id_counter = 0
|
||||
|
||||
export const upload_files = async (files: File[]|FileList) => {
|
||||
@@ -76,8 +78,8 @@ export const upload_file = async (file: File) => {
|
||||
// each upload progress bar will have bound itself to its array item
|
||||
upload_queue = upload_queue
|
||||
|
||||
if (active_uploads === 0 && state !== "uploading") {
|
||||
state = "uploading"
|
||||
if (active_uploads === 0 && status !== "uploading") {
|
||||
status = "uploading"
|
||||
visible = true
|
||||
await tick()
|
||||
await start_upload()
|
||||
@@ -85,7 +87,7 @@ export const upload_file = async (file: File) => {
|
||||
}
|
||||
|
||||
let active_uploads = 0
|
||||
let state = "idle"
|
||||
let status = $state("idle")
|
||||
|
||||
const start_upload = async () => {
|
||||
active_uploads = 0
|
||||
@@ -115,7 +117,7 @@ const start_upload = async () => {
|
||||
}
|
||||
|
||||
if (active_uploads === 0) {
|
||||
state = "finished"
|
||||
status = "finished"
|
||||
nav.reload()
|
||||
|
||||
// Empty the queue to free any references to lingering components
|
||||
@@ -123,12 +125,12 @@ const start_upload = async () => {
|
||||
|
||||
// In ten seconds we close the popup
|
||||
setTimeout(() => {
|
||||
if (state === "finished") {
|
||||
if (status === "finished") {
|
||||
visible = false
|
||||
}
|
||||
}, 10000)
|
||||
} else {
|
||||
state = "uploading"
|
||||
status = "uploading"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +141,7 @@ const finish_upload = () => {
|
||||
}
|
||||
|
||||
const leave_confirmation = (e: BeforeUnloadEvent) => {
|
||||
if (state === "uploading") {
|
||||
if (status === "uploading") {
|
||||
e.preventDefault()
|
||||
return "If you close this page your files will stop uploading. Do you want to continue?"
|
||||
} else {
|
||||
@@ -148,22 +150,22 @@ const leave_confirmation = (e: BeforeUnloadEvent) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:beforeunload={leave_confirmation} />
|
||||
<svelte:window onbeforeunload={leave_confirmation} />
|
||||
|
||||
<input
|
||||
bind:this={file_input_field}
|
||||
on:change={file_input_change}
|
||||
onchange={file_input_change}
|
||||
class="upload_input" type="file" name="file" multiple
|
||||
/>
|
||||
|
||||
{#if visible}
|
||||
<div class="upload_widget">
|
||||
<div class="header">
|
||||
{#if state === "idle"}
|
||||
{#if status === "idle"}
|
||||
Waiting for files
|
||||
{:else if state === "uploading"}
|
||||
{:else if status === "uploading"}
|
||||
Uploading files...
|
||||
{:else if state === "finished"}
|
||||
{:else if status === "finished"}
|
||||
Done
|
||||
{/if}
|
||||
</div>
|
||||
|
@@ -6,11 +6,19 @@ import Button from "layout/Button.svelte"
|
||||
import type { UploadJob } from "./FSUploadWidget.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
export let job: UploadJob
|
||||
export let total = 0
|
||||
export let loaded = 0
|
||||
let error_code = ""
|
||||
let error_message = ""
|
||||
|
||||
let {
|
||||
job = $bindable(),
|
||||
total = $bindable(0),
|
||||
loaded = $bindable(0)
|
||||
}: {
|
||||
job: UploadJob;
|
||||
total?: number;
|
||||
loaded?: number;
|
||||
} = $props();
|
||||
|
||||
let error_code = $state("")
|
||||
let error_message = $state("")
|
||||
let xhr: XMLHttpRequest = null
|
||||
|
||||
export const start = () => {
|
||||
|
@@ -1,10 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let path = ""
|
||||
let {
|
||||
nav,
|
||||
path = "",
|
||||
children
|
||||
}: {
|
||||
nav: FSNavigator;
|
||||
path?: string;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<a href={"/d"+path} on:click|preventDefault={() => {nav.navigate(path, true)}}>
|
||||
<slot></slot>
|
||||
<a href={"/d"+path} onclick={preventDefault(() => {nav.navigate(path, true)})}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
|
@@ -1,14 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { onMount } from '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';
|
||||
|
||||
export let nav: FSNavigator
|
||||
let player: HTMLAudioElement
|
||||
let playing = false
|
||||
let { nav, children }: {
|
||||
nav: FSNavigator;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let player: HTMLAudioElement = $state()
|
||||
let playing = $state(false)
|
||||
let media_session = false
|
||||
let siblings = []
|
||||
let siblings = $state([])
|
||||
|
||||
export const toggle_playback = () => playing ? player.pause() : player.play()
|
||||
export const toggle_mute = () => player.muted = !player.muted
|
||||
@@ -47,7 +52,7 @@ onMount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
<TextBlock width="1000px">
|
||||
<audio
|
||||
@@ -56,30 +61,30 @@ onMount(() => {
|
||||
src={fs_path_url($nav.base.path)}
|
||||
autoplay
|
||||
controls
|
||||
on:pause={() => playing = false }
|
||||
on:play={() => playing = true }
|
||||
on:ended={() => nav.open_sibling(1) }>
|
||||
onpause={() => playing = false}
|
||||
onplay={() => playing = true}
|
||||
onended={() => nav.open_sibling(1)}>
|
||||
<track kind="captions"/>
|
||||
</audio>
|
||||
<div style="text-align: center;">
|
||||
<button on:click={() => nav.open_sibling(-1) }><i class="icon">skip_previous</i></button>
|
||||
<button on:click={() => seek(-10) }><i class="icon">replay_10</i></button>
|
||||
<button on:click={toggle_playback}>
|
||||
<button onclick={() => nav.open_sibling(-1)}><i class="icon">skip_previous</i></button>
|
||||
<button onclick={() => seek(-10)}><i class="icon">replay_10</i></button>
|
||||
<button onclick={toggle_playback}>
|
||||
{#if playing}
|
||||
<i class="icon">pause</i>
|
||||
{:else}
|
||||
<i class="icon">play_arrow</i>
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={() => seek(10) }><i class="icon">forward_10</i></button>
|
||||
<button on:click={() => nav.open_sibling(1) }><i class="icon">skip_next</i></button>
|
||||
<button onclick={() => seek(10)}><i class="icon">forward_10</i></button>
|
||||
<button onclick={() => nav.open_sibling(1)}><i class="icon">skip_next</i></button>
|
||||
</div>
|
||||
|
||||
<h2>Tracklist</h2>
|
||||
{#each siblings as sibling (sibling.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(sibling.path)}
|
||||
on:click|preventDefault={() => nav.navigate(sibling.path, true)}
|
||||
onclick={preventDefault(() => nav.navigate(sibling.path, true))}
|
||||
class="node"
|
||||
>
|
||||
{#if sibling.path === $nav.base.path}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
export let path = []
|
||||
import type { FSNode } from 'lib/FilesystemAPI';
|
||||
import { run } from 'svelte/legacy';
|
||||
|
||||
let image_uri: string
|
||||
let image_link: string
|
||||
$: update_links(path)
|
||||
const update_links = (path) => {
|
||||
let { path = [] }: {path: FSNode[]} = $props();
|
||||
|
||||
let image_uri: string = $state()
|
||||
let image_link: string = $state()
|
||||
const update_links = (path: FSNode[]) => {
|
||||
image_uri = null
|
||||
image_link = null
|
||||
for (let node of path) {
|
||||
@@ -18,6 +20,9 @@ const update_links = (path) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
run(() => {
|
||||
update_links(path)
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if image_uri}
|
||||
|
@@ -8,10 +8,13 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav, children }: {
|
||||
nav: FSNavigator;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
@@ -20,11 +23,11 @@ export let nav: FSNavigator
|
||||
Size: {formatDataVolume($nav.base.file_size, 3)}<br/>
|
||||
Upload date: {formatDate($nav.base.created, true, true, false)}
|
||||
<hr/>
|
||||
<button class="button_highlight" on:click={() => {dispatch("download")}}>
|
||||
<button class="button_highlight" onclick={() => {dispatch("download")}}>
|
||||
<i class="icon">download</i>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button on:click={() => {dispatch("details")}}>
|
||||
<button onclick={() => {dispatch("details")}}>
|
||||
<i class="icon">help</i>
|
||||
<span>Details</span>
|
||||
</button>
|
||||
|
@@ -16,12 +16,14 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
|
||||
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
|
||||
|
||||
export let nav: FSNavigator
|
||||
export let upload_widget: FsUploadWidget
|
||||
export let edit_window: EditWindow
|
||||
let { nav, upload_widget, edit_window }: {
|
||||
nav: FSNavigator;
|
||||
upload_widget: FsUploadWidget;
|
||||
edit_window: EditWindow;
|
||||
} = $props();
|
||||
|
||||
let viewer: any
|
||||
let viewer_type = ""
|
||||
let viewer: any = $state()
|
||||
let viewer_type = $state("")
|
||||
let last_path = ""
|
||||
|
||||
onMount(() => nav.subscribe(state_update))
|
||||
|
@@ -6,13 +6,16 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
export let nav: FSNavigator
|
||||
let container: HTMLDivElement
|
||||
let zoom = false
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
let container: HTMLDivElement = $state()
|
||||
let zoom = $state(false)
|
||||
let x = 0, y = 0
|
||||
let dragging = false
|
||||
let swipe_prev = true
|
||||
let swipe_next = true
|
||||
let swipe_prev = $state(true)
|
||||
let swipe_next = $state(true)
|
||||
|
||||
export const update = async () => {
|
||||
dispatch("loading", true)
|
||||
@@ -66,7 +69,7 @@ const mouseup = (e: MouseEvent) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
|
||||
<svelte:window onmousemove={mousemove} onmouseup={mouseup} />
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
@@ -80,12 +83,12 @@ const mouseup = (e: MouseEvent) => {
|
||||
on_next: () => nav.open_sibling(1),
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<img
|
||||
on:dblclick={() => {zoom = !zoom}}
|
||||
on:mousedown={mousedown}
|
||||
on:load={on_load}
|
||||
on:error={on_load}
|
||||
ondblclick={() => {zoom = !zoom}}
|
||||
onmousedown={mousedown}
|
||||
onload={on_load}
|
||||
onerror={on_load}
|
||||
class="image"
|
||||
class:zoom
|
||||
src={fs_path_url($nav.base.path)}
|
||||
|
@@ -2,7 +2,9 @@
|
||||
import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<iframe
|
||||
|
@@ -3,8 +3,12 @@ import { tick } from "svelte";
|
||||
import { fs_path_url, type FSNode } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
|
||||
export let nav: FSNavigator
|
||||
let text_type = "text"
|
||||
let { nav, children }: {
|
||||
nav: FSNavigator;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let text_type = $state("text")
|
||||
|
||||
export const update = () => {
|
||||
console.debug("Loading text file", nav.base.name)
|
||||
@@ -25,7 +29,7 @@ export const update = () => {
|
||||
}
|
||||
}
|
||||
|
||||
let text_pre: HTMLPreElement
|
||||
let text_pre: HTMLPreElement = $state()
|
||||
const text = async (file: FSNode) => {
|
||||
text_type = "text"
|
||||
await tick()
|
||||
@@ -42,7 +46,7 @@ const text = async (file: FSNode) => {
|
||||
})
|
||||
}
|
||||
|
||||
let md_container: HTMLElement
|
||||
let md_container: HTMLElement = $state()
|
||||
const markdown = async (file: FSNode) => {
|
||||
text_type = "markdown"
|
||||
await tick()
|
||||
@@ -61,7 +65,7 @@ const markdown = async (file: FSNode) => {
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
{#if text_type === "markdown"}
|
||||
<section bind:this={md_container} class="md">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
export type TorrentInfo = {
|
||||
trackers: string[]
|
||||
comment: string,
|
||||
@@ -26,9 +26,12 @@ import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav, children }: {
|
||||
nav: FSNavigator;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let status = "loading"
|
||||
let status = $state("loading")
|
||||
|
||||
export const update = async () => {
|
||||
try {
|
||||
@@ -64,11 +67,11 @@ export const update = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let torrent: TorrentInfo = {} as TorrentInfo
|
||||
let magnet = ""
|
||||
let torrent: TorrentInfo = $state({} as TorrentInfo)
|
||||
let magnet = $state("")
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
@@ -93,7 +96,7 @@ let magnet = ""
|
||||
Torrent file could not be parsed. It may be corrupted.
|
||||
</p>
|
||||
{/if}
|
||||
<button on:click={() => {dispatch("download")}} class="button">
|
||||
<button onclick={() => {dispatch("download")}} class="button">
|
||||
<i class="icon">download</i>
|
||||
<span>Download torrent file</span>
|
||||
</button>
|
||||
|
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import TorrentItem from './TorrentItem.svelte';
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
import type { TorrentFile } from "./Torrent.svelte";
|
||||
|
||||
export let item: TorrentFile = {} as TorrentFile
|
||||
let { item = {} as TorrentFile }: {
|
||||
item?: TorrentFile;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<ul class="list_open">
|
||||
@@ -10,7 +13,7 @@ export let item: TorrentFile = {} as TorrentFile
|
||||
<li class:list_closed={!child.children}>
|
||||
{name} ({formatDataVolume(child.size, 3)})<br/>
|
||||
{#if child.children}
|
||||
<svelte:self item={child}></svelte:self>
|
||||
<TorrentItem item={child}></TorrentItem>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
|
@@ -5,16 +5,18 @@ import { fs_path_url } from "lib/FilesystemAPI";
|
||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav }: {
|
||||
nav: FSNavigator;
|
||||
} = $props();
|
||||
|
||||
// Used to detect when the file path changes
|
||||
let last_path = ""
|
||||
let loaded = false
|
||||
let loaded = $state(false)
|
||||
|
||||
let player: HTMLVideoElement
|
||||
let playing = false
|
||||
let player: HTMLVideoElement = $state()
|
||||
let playing = $state(false)
|
||||
let media_session = false
|
||||
let loop = false
|
||||
let loop = $state(false)
|
||||
|
||||
export const update = async () => {
|
||||
if (media_session) {
|
||||
@@ -85,8 +87,7 @@ const video_keydown = (e: KeyboardEvent) => {
|
||||
{#if
|
||||
$nav.base.file_type === "video/x-matroska" ||
|
||||
$nav.base.file_type === "video/quicktime" ||
|
||||
$nav.base.file_type === "video/x-ms-asf"
|
||||
}
|
||||
$nav.base.file_type === "video/x-ms-asf"}
|
||||
<div class="compatibility_warning">
|
||||
This video file type is not compatible with every web
|
||||
browser. If the video fails to play you can try downloading
|
||||
@@ -96,7 +97,7 @@ const video_keydown = (e: KeyboardEvent) => {
|
||||
<div class="player_and_controls">
|
||||
<div class="player">
|
||||
{#if loaded}
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video
|
||||
bind:this={player}
|
||||
controls
|
||||
@@ -104,9 +105,9 @@ const video_keydown = (e: KeyboardEvent) => {
|
||||
autoplay
|
||||
loop={loop}
|
||||
class="video"
|
||||
on:pause={() => playing = false }
|
||||
on:play={() => playing = true }
|
||||
on:keydown={video_keydown}
|
||||
onpause={() => playing = false}
|
||||
onplay={() => playing = true}
|
||||
onkeydown={video_keydown}
|
||||
use:video_position={() => $nav.base.sha256_sum.substring(0, 8)}
|
||||
>
|
||||
<source src={fs_path_url($nav.base.path)} type={$nav.base.file_type} />
|
||||
@@ -116,34 +117,34 @@ const video_keydown = (e: KeyboardEvent) => {
|
||||
|
||||
<div class="controls">
|
||||
<div class="spacer"></div>
|
||||
<button on:click={() => dispatch("open_sibling", -1) }>
|
||||
<button onclick={() => dispatch("open_sibling", -1)}>
|
||||
<i class="icon">skip_previous</i>
|
||||
</button>
|
||||
<button on:click={() => seek(-10)}>
|
||||
<button onclick={() => seek(-10)}>
|
||||
<i class="icon">replay_10</i>
|
||||
</button>
|
||||
<button on:click={toggle_playback} class="button_highlight">
|
||||
<button onclick={toggle_playback} class="button_highlight">
|
||||
{#if playing}
|
||||
<i class="icon">pause</i>
|
||||
{:else}
|
||||
<i class="icon">play_arrow</i>
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={() => seek(10)}>
|
||||
<button onclick={() => seek(10)}>
|
||||
<i class="icon">forward_10</i>
|
||||
</button>
|
||||
<button on:click={() => dispatch("open_sibling", 1) }>
|
||||
<button onclick={() => dispatch("open_sibling", 1)}>
|
||||
<i class="icon">skip_next</i>
|
||||
</button>
|
||||
<div style="width: 16px; height: 8px;"></div>
|
||||
<button on:click={toggle_mute} class:button_red={player && player.muted}>
|
||||
<button onclick={toggle_mute} class:button_red={player && player.muted}>
|
||||
{#if player && player.muted}
|
||||
<i class="icon">volume_off</i>
|
||||
{:else}
|
||||
<i class="icon">volume_up</i>
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={toggle_fullscreen}>
|
||||
<button onclick={toggle_fullscreen}>
|
||||
<i class="icon">fullscreen</i>
|
||||
</button>
|
||||
<div class="spacer"></div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
export type ZipEntry = {
|
||||
size: number,
|
||||
children?: {[index: string]: ZipEntry},
|
||||
@@ -19,15 +19,18 @@ import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let nav: FSNavigator
|
||||
let { nav, children }: {
|
||||
nav: FSNavigator;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let status = "loading"
|
||||
let status = $state("loading")
|
||||
|
||||
let zip: ZipEntry = {size: 0} as ZipEntry
|
||||
let zip: ZipEntry = $state({size: 0} as ZipEntry)
|
||||
let uncomp_size = 0
|
||||
let comp_ratio = 0
|
||||
let archive_type = ""
|
||||
let truncated = false
|
||||
let comp_ratio = $state(0)
|
||||
let archive_type = $state("")
|
||||
let truncated = $state(false)
|
||||
|
||||
export const update = async () => {
|
||||
if (nav.base.file_type === "application/x-7z-compressed") {
|
||||
@@ -93,7 +96,7 @@ const recursive_size = (file: ZipEntry) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
@@ -110,7 +113,7 @@ const recursive_size = (file: ZipEntry) => {
|
||||
{/if}
|
||||
Uploaded on: {formatDate($nav.base.created, true, true, true)}
|
||||
<br/>
|
||||
<button class="button_highlight" on:click={() => {dispatch("download")}}>
|
||||
<button class="button_highlight" onclick={() => {dispatch("download")}}>
|
||||
<i class="icon">download</i>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
|
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import ZipItem from './ZipItem.svelte';
|
||||
import type { ZipEntry } from "filesystem/viewers/Zip.svelte";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
|
||||
export let item: ZipEntry = {} as ZipEntry
|
||||
let { item = {} as ZipEntry }: {
|
||||
item?: ZipEntry;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<!-- First get directories and render them as details collapsibles -->
|
||||
@@ -23,7 +26,7 @@ export let item: ZipEntry = {} as ZipEntry
|
||||
|
||||
<!-- Performance optimization, only render children if details is expanded -->
|
||||
{#if child.details_open}
|
||||
<svelte:self item={child}></svelte:self>
|
||||
<ZipItem item={child}></ZipItem>
|
||||
{/if}
|
||||
</details>
|
||||
{/if}
|
||||
|
@@ -3,7 +3,7 @@ import { onMount } from "svelte";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
let result = null;
|
||||
let result = $state(null);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
@@ -21,9 +21,11 @@ onMount(async () => {
|
||||
{#if result !== null && result.user_banned}
|
||||
<section>
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header red">
|
||||
{#snippet header()}
|
||||
<div class="header red">
|
||||
Your account has been banned, click for details
|
||||
</div>
|
||||
{/snippet}
|
||||
<p>
|
||||
Your user account has been banned from uploading to
|
||||
pixeldrain due to violation of the
|
||||
@@ -72,13 +74,16 @@ onMount(async () => {
|
||||
{:else if result !== null && result.ip_offences.length > 0}
|
||||
<section>
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header" class:red={result.ip_banned} class:yellow={!result.ip_banned}>
|
||||
{#snippet header()}
|
||||
<div class="header" class:red={result.ip_banned} class:yellow={!result.ip_banned}>
|
||||
{#if result.ip_banned}
|
||||
Your IP address has been banned, click for details
|
||||
{:else}
|
||||
Your IP address has received a copyright strike, click for details
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if result.ip_banned}
|
||||
<p>
|
||||
Your IP address ({result.address}) has been banned from
|
||||
|
@@ -36,10 +36,12 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell pro_feat">
|
||||
<Tooltip>
|
||||
<span slot="label">
|
||||
{#snippet label()}
|
||||
<span>
|
||||
<span class="bold">€4 / month</span> or
|
||||
<span class="bold">€40 / year</span>
|
||||
</span>
|
||||
{/snippet}
|
||||
The Pro subscription is managed by Patreon. Patreon's own fees
|
||||
and sales tax will be added to this price. After paying you need
|
||||
to link your pixeldrain account to Patreon to activate the plan.
|
||||
@@ -47,7 +49,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell prepaid_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">€1 / month minimum</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">€1 / month minimum</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
The minimum fee is only charged when usage is less than €1.
|
||||
This calculation is per day, the €1 amount is divided by the
|
||||
@@ -62,7 +66,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell free_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">6 GB per day</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">6 GB per day</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
Free users are limited to downloading 6 GB per day, this
|
||||
limit is linked to your IP address, even if you are logged
|
||||
@@ -77,7 +83,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell pro_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">4 TB per 30 days</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">4 TB per 30 days</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
The transfer limit is used for downloading, sharing and
|
||||
hotlinking files.
|
||||
@@ -89,7 +97,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell prepaid_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">€1 per TB transferred</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">€1 per TB transferred</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
Prepaid does not have a transfer limit, instead you are
|
||||
charged for what you use at a rate of €1 per terabyte
|
||||
@@ -132,7 +142,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell free_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">120 days (4 months)</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">120 days (4 months)</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
Files expire when they have not been downloaded in the last
|
||||
120 days. A download is counted when more than 10% of the
|
||||
@@ -142,7 +154,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell pro_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">240 days (8 months)</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">240 days (8 months)</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
The Pro plan has 240 day file expiry. The same rules apply
|
||||
as the free plan. Higher Patreon subscription plans are
|
||||
@@ -152,7 +166,9 @@ import OtherPlans from "./OtherPlans.svelte";
|
||||
</div>
|
||||
<div class="feature_cell prepaid_feat">
|
||||
<Tooltip>
|
||||
<span slot="label" class="bold">Files do not expire</span>
|
||||
{#snippet label()}
|
||||
<span class="bold">Files do not expire</span>
|
||||
{/snippet}
|
||||
<p>
|
||||
Files don't expire while your Prepaid plan is active. If
|
||||
your credit runs out you have one week to top up your
|
||||
|
@@ -1,63 +0,0 @@
|
||||
<script>
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">
|
||||
Important policy change! Click to expand
|
||||
</div>
|
||||
<p>
|
||||
Sometime this summer pixeldrain will start requiring you to be
|
||||
logged in to an account in order to upload new files. This is
|
||||
necessary due to the large amount of regional blockings that have
|
||||
been implemented against pixeldrain recently.
|
||||
</p>
|
||||
<p>
|
||||
None of the countries that have blocked pixeldrain have specified a
|
||||
valid reason, nor have I been able to contact them to ask what's
|
||||
going on. But I can only guess that it has something to do with
|
||||
abusive content being uploaded. Pixeldrain currently uses an IP
|
||||
address banning system for restricting uploads from users who have
|
||||
violated the content policy, but at the scale the site operates at
|
||||
now that has proven to not be effective anymore. For that reason
|
||||
pixeldrain will be switching to an account ban system.
|
||||
</p>
|
||||
<p>
|
||||
The account ban system will restrict file uploads to your account
|
||||
for a certain amount of time which depends on the type of the
|
||||
offence. Serious violations might get your account banned for up to
|
||||
a year, minor violations maybe a day or a week per file. It will
|
||||
depend on the amount of abuse that slips through. Once the account
|
||||
ban system is activated the IP ban system will be disabled.
|
||||
</p>
|
||||
<p>
|
||||
Account banning is only effective if there is some system in place
|
||||
to prevent the automated creation of accounts. There is already a
|
||||
CAPTCHA in place on the registration page. I might start requiring
|
||||
e-mail addresses for new accounts as well. The lack of an e-mail
|
||||
address requirement has caused many issues with lost accounts as
|
||||
well, so it had to happen anyway. I will also be looking into
|
||||
integrating with OAuth providers (Google, Microsoft, Facebook,
|
||||
Patreon, etc) to make the login flow simpler for newcomers.
|
||||
</p>
|
||||
<p>
|
||||
After the site has been cleaned up it might take a long time before
|
||||
all the regional blocking issues are resolved. Because of that I
|
||||
will be adding alternative domain names for premium subscribers to
|
||||
use. Most of the blocks are only on DNS level, which means they can
|
||||
be circumvented by using a different domain name.
|
||||
</p>
|
||||
</Expandable>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(0, 0, 255, 0.2);
|
||||
}
|
||||
</style>
|
@@ -1,27 +1,28 @@
|
||||
<script>
|
||||
import { run } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import ProgressBar from "util/ProgressBar.svelte";
|
||||
|
||||
let pixeldrain_storage = 0
|
||||
let pixeldrain_egress = 0
|
||||
let pixeldrain_total = 0
|
||||
let backblaze_storage = 0
|
||||
let backblaze_egress = 0
|
||||
let backblaze_api = 0
|
||||
let backblaze_total = 0
|
||||
let wasabi_storage = 0
|
||||
let wasabi_total = 0
|
||||
let pixeldrain_storage = $state(0)
|
||||
let pixeldrain_egress = $state(0)
|
||||
let pixeldrain_total = $state(0)
|
||||
let backblaze_storage = $state(0)
|
||||
let backblaze_egress = $state(0)
|
||||
let backblaze_api = $state(0)
|
||||
let backblaze_total = $state(0)
|
||||
let wasabi_storage = $state(0)
|
||||
let wasabi_total = $state(0)
|
||||
let price_amazon = 0
|
||||
let price_azure = 0
|
||||
let price_google = 0
|
||||
let price_max = 0
|
||||
let price_max = $state(0)
|
||||
|
||||
let storage = 10 // TB
|
||||
let egress = 10 // TB
|
||||
let avg_file_size = 1000 // kB
|
||||
let storage = $state(10) // TB
|
||||
let egress = $state(10) // TB
|
||||
let avg_file_size = $state(1000) // kB
|
||||
|
||||
$: {
|
||||
run(() => {
|
||||
pixeldrain_storage = storage * 4
|
||||
pixeldrain_egress = egress * 1
|
||||
pixeldrain_total = pixeldrain_storage + pixeldrain_egress
|
||||
@@ -43,7 +44,7 @@ $: {
|
||||
// price_google = (storage * 20) + (egress * 20)
|
||||
|
||||
price_max = Math.max(pixeldrain_total, backblaze_total, wasabi_total, price_amazon, price_azure, price_google)
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {})
|
||||
</script>
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} width="24" height="24" viewBox="0 -28.5 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style = "" }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} role="img" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style = "" }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 987.525 987.525">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style = "" }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 216.4144 232.00976">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style = "" }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 100 100">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let style: string;
|
||||
let { style = "" }: {
|
||||
style: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
|
||||
|
@@ -1,20 +1,39 @@
|
||||
<script lang="ts">
|
||||
export let highlight = false;
|
||||
export let highlight_on_click = false
|
||||
export let red = false;
|
||||
export let round = false;
|
||||
export let flat = false;
|
||||
export let disabled = false;
|
||||
export let icon = ""
|
||||
export let icon_small = false;
|
||||
export let label = ""
|
||||
export let title = null
|
||||
export let link_href = ""
|
||||
export let link_target = "_self"
|
||||
export let click: (e?: MouseEvent) => void = null
|
||||
export let style = null
|
||||
export let type = null
|
||||
export let form = null
|
||||
let {
|
||||
highlight = $bindable(false),
|
||||
highlight_on_click = false,
|
||||
red = $bindable(false),
|
||||
round = false,
|
||||
flat = false,
|
||||
disabled = false,
|
||||
icon = "",
|
||||
icon_small = false,
|
||||
label = "",
|
||||
title = null,
|
||||
link_href = "",
|
||||
link_target = "_self",
|
||||
click = null,
|
||||
style = null,
|
||||
type = null,
|
||||
form = null
|
||||
}: {
|
||||
highlight?: boolean;
|
||||
highlight_on_click?: boolean;
|
||||
red?: boolean;
|
||||
round?: boolean;
|
||||
flat?: boolean;
|
||||
disabled?: boolean;
|
||||
icon?: string;
|
||||
icon_small?: boolean;
|
||||
label?: string;
|
||||
title?: any;
|
||||
link_href?: string;
|
||||
link_target?: string;
|
||||
click?: (e?: MouseEvent) => void;
|
||||
style?: any;
|
||||
type?: any;
|
||||
form?: any;
|
||||
} = $props();
|
||||
|
||||
let click_int = (e: MouseEvent) => {
|
||||
if (highlight_on_click) {
|
||||
@@ -33,7 +52,7 @@ let click_int = (e: MouseEvent) => {
|
||||
|
||||
{#if link_href === ""}
|
||||
<button
|
||||
on:click={click_int}
|
||||
onclick={click_int}
|
||||
class="button"
|
||||
class:button_highlight={highlight}
|
||||
class:button_red={red}
|
||||
@@ -54,7 +73,7 @@ let click_int = (e: MouseEvent) => {
|
||||
</button>
|
||||
{:else}
|
||||
<a
|
||||
on:click={click_int}
|
||||
onclick={click_int}
|
||||
href="{link_href}"
|
||||
target={link_target}
|
||||
class="button"
|
||||
|
@@ -1,12 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { copy_text } from "util/Util.svelte";
|
||||
import { copy_text } from "util/Util";
|
||||
|
||||
export let text = ""
|
||||
export let style = ""
|
||||
export let large_icon = false
|
||||
export let small_icon = false
|
||||
let failed = false
|
||||
let success = false
|
||||
let {
|
||||
text = "",
|
||||
style = "",
|
||||
large_icon = false,
|
||||
small_icon = false,
|
||||
children
|
||||
}: {
|
||||
text?: string;
|
||||
style?: string;
|
||||
large_icon?: boolean;
|
||||
small_icon?: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let failed = $state(false)
|
||||
let success = $state(false)
|
||||
|
||||
// Exported so it can be called manually
|
||||
export const copy = () => {
|
||||
@@ -26,7 +36,7 @@ export const copy = () => {
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={copy}
|
||||
onclick={copy}
|
||||
style={style}
|
||||
class="button"
|
||||
class:button_highlight={success}
|
||||
@@ -43,7 +53,7 @@ export const copy = () => {
|
||||
{:else if failed}
|
||||
Copy failed
|
||||
{:else}
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
</span>
|
||||
</button>
|
||||
|
@@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
let dialog: HTMLDialogElement
|
||||
let { children }: {
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
let dialog: HTMLDialogElement = $state()
|
||||
|
||||
export const open = (button_rect: DOMRect) => {
|
||||
// Show the window so we can get the location
|
||||
@@ -37,10 +40,10 @@ const click = (e: MouseEvent) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<dialog bind:this={dialog} on:click={click}>
|
||||
<slot></slot>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<dialog bind:this={dialog} onclick={click}>
|
||||
{@render children?.()}
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let title = ""
|
||||
let { title = "" }: {
|
||||
title?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<h1>{title}</h1>
|
||||
|
@@ -8,10 +8,13 @@ import Reddit from "icons/Reddit.svelte";
|
||||
import { formatDataVolumeBits } from "util/Formatting";
|
||||
import { get_endpoint, get_hostname } from "lib/PixeldrainAPI";
|
||||
|
||||
export let nobg = false
|
||||
let server_tx = 0
|
||||
let cache_tx = 0
|
||||
let storage_tx = 0
|
||||
let { nobg = false }: {
|
||||
nobg?: boolean;
|
||||
} = $props();
|
||||
|
||||
let server_tx = $state(0)
|
||||
let cache_tx = $state(0)
|
||||
let storage_tx = $state(0)
|
||||
onMount(async () => {
|
||||
try {
|
||||
const resp = await fetch(get_endpoint()+"/misc/cluster_speed")
|
||||
|
@@ -1,11 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let toggle = false;
|
||||
let {
|
||||
toggle = $bindable(false)
|
||||
}: {
|
||||
toggle?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<button class="button small_button round"
|
||||
class:button_highlight={toggle}
|
||||
style="margin: 0;"
|
||||
on:click={() => toggle = !toggle}
|
||||
onclick={() => toggle = !toggle}
|
||||
>
|
||||
<i class="icon">help</i>
|
||||
</button>
|
||||
|
@@ -1,12 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let icon_href = ""
|
||||
export let width = "750px"
|
||||
let {
|
||||
icon_href = "",
|
||||
width = "750px",
|
||||
children
|
||||
}: {
|
||||
icon_href?: string;
|
||||
width?: string;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="block" style="width: {width}; max-width: 100%">
|
||||
<img src={icon_href} alt="File icon" class="icon">
|
||||
<div class="description">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { user } from "lib/UserStore";
|
||||
|
||||
let nav: HTMLElement;
|
||||
let nav: HTMLElement = $state();
|
||||
|
||||
export const toggle = () => {
|
||||
var body = document.getElementById("page_body");
|
||||
@@ -21,7 +21,7 @@ export const reset = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<button id="button_toggle_navigation" class="button_toggle_navigation" on:click={toggle}>☰</button>
|
||||
<button id="button_toggle_navigation" class="button_toggle_navigation" onclick={toggle}>☰</button>
|
||||
<nav bind:this={nav} id="page_navigation" class="page_navigation">
|
||||
<a href="/#">Home</a>
|
||||
<a href="/#prepaid">For Creators</a>
|
||||
|
@@ -1,15 +1,24 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { formatDataVolume, formatDuration } from "util/Formatting";
|
||||
import { stats } from "lib/StatsSocket"
|
||||
import TextBlock from "./TextBlock.svelte"
|
||||
import IconBlock from "./IconBlock.svelte";
|
||||
import { user } from "lib/UserStore";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let file_size = 0
|
||||
export let file_name = ""
|
||||
export let file_type = ""
|
||||
export let icon_href = ""
|
||||
let {
|
||||
file_size = 0,
|
||||
file_name = "",
|
||||
file_type = "",
|
||||
icon_href = ""
|
||||
}: {
|
||||
file_size?: number;
|
||||
file_name?: string;
|
||||
file_type?: string;
|
||||
icon_href?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<TextBlock>
|
||||
@@ -41,7 +50,7 @@ export let icon_href = ""
|
||||
<i class="icon">bolt</i> Get Premium
|
||||
</a>
|
||||
and earn my eternal gratitude
|
||||
{#if !window.user_authenticated}
|
||||
{#if $user.username === undefined || $user.username === ""}
|
||||
(you will need a <a href="/register">pixeldrain account</a> to
|
||||
receive the benefits)
|
||||
{/if}
|
||||
@@ -57,7 +66,7 @@ export let icon_href = ""
|
||||
<tr><td>Size</td><td>{formatDataVolume(file_size, 3)}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button on:click={() => {dispatch("download")}}>
|
||||
<button onclick={() => {dispatch("download")}}>
|
||||
<i class="icon">download</i> Download
|
||||
</button>
|
||||
</IconBlock>
|
||||
|
@@ -1,15 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let field = ""
|
||||
export let active_field = ""
|
||||
export let asc = true
|
||||
export let sort_func: (field: string) => void
|
||||
let {
|
||||
field = "",
|
||||
active_field = "",
|
||||
asc = true,
|
||||
sort_func,
|
||||
children
|
||||
}: {
|
||||
field?: string;
|
||||
active_field?: string;
|
||||
asc?: boolean;
|
||||
sort_func: (field: string) => void;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<button on:click={() => sort_func(field)}>
|
||||
<button onclick={() => sort_func(field)}>
|
||||
{#if active_field === field}
|
||||
{#if asc}↓{:else}↑{/if}
|
||||
{/if}
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
// svelte-ignore non_reactive_update
|
||||
export enum FieldType {
|
||||
Text,
|
||||
Bytes,
|
||||
Bits,
|
||||
Number,
|
||||
Euro,
|
||||
HTML,
|
||||
Func,
|
||||
Text = 1,
|
||||
Bytes = 2,
|
||||
Bits = 3,
|
||||
Number = 4,
|
||||
Euro = 5,
|
||||
HTML = 6,
|
||||
Func = 7,
|
||||
}
|
||||
export type SortColumn = {
|
||||
field: string,
|
||||
@@ -20,13 +21,21 @@ import SortButton from "layout/SortButton.svelte";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { formatDataVolume, formatDataVolumeBits, formatThousands } from "util/Formatting";
|
||||
|
||||
export let index_field = ""
|
||||
export let rows = [];
|
||||
export let columns: SortColumn[] = []
|
||||
export let sort_field = index_field
|
||||
export let totals = false
|
||||
let {
|
||||
index_field = "",
|
||||
rows = $bindable([]),
|
||||
columns = [],
|
||||
sort_field = $bindable(index_field),
|
||||
totals = false
|
||||
}: {
|
||||
index_field?: string;
|
||||
rows?: any;
|
||||
columns?: SortColumn[];
|
||||
sort_field?: any;
|
||||
totals?: boolean;
|
||||
} = $props();
|
||||
|
||||
let asc = true
|
||||
let asc = $state(true)
|
||||
const sort = (field: string) => {
|
||||
if (field !== "" && field === sort_field) {
|
||||
asc = !asc
|
||||
|
@@ -1,10 +1,17 @@
|
||||
<script>
|
||||
export let width = "750px"
|
||||
export let center = false
|
||||
<script lang="ts">
|
||||
let {
|
||||
width = "750px",
|
||||
center = false,
|
||||
children
|
||||
}: {
|
||||
width?: string;
|
||||
center?: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="block" class:center style="width: {width};">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@@ -1,11 +1,23 @@
|
||||
<script lang="ts">
|
||||
export let on = false
|
||||
export let icon_on = "check"
|
||||
export let icon_off = "close"
|
||||
export let group_first = false
|
||||
export let group_middle = false
|
||||
export let group_last = false
|
||||
export let action = (e: MouseEvent) => {}
|
||||
let {
|
||||
on = $bindable(),
|
||||
icon_on = "check",
|
||||
icon_off = "close",
|
||||
group_first = false,
|
||||
group_middle = false,
|
||||
group_last = false,
|
||||
action,
|
||||
children,
|
||||
}: {
|
||||
on?: boolean;
|
||||
icon_on?: string;
|
||||
icon_off?: string;
|
||||
group_first?: boolean;
|
||||
group_middle?: boolean;
|
||||
group_last?: boolean;
|
||||
action?: (e: MouseEvent) => void;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
const click = (e: MouseEvent) => {
|
||||
on = !on
|
||||
@@ -16,7 +28,7 @@ const click = (e: MouseEvent) => {
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={click}
|
||||
onclick={click}
|
||||
type="button"
|
||||
class="button"
|
||||
class:button_highlight={on}
|
||||
@@ -29,7 +41,7 @@ const click = (e: MouseEvent) => {
|
||||
{:else}
|
||||
<i class="icon">{icon_off}</i>
|
||||
{/if}
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
@@ -1,19 +1,24 @@
|
||||
<script lang="ts">
|
||||
import Dialog from "./Dialog.svelte";
|
||||
|
||||
let button: HTMLButtonElement
|
||||
let dialog: Dialog
|
||||
let { label, children }: {
|
||||
label?: import('svelte').Snippet;
|
||||
children?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
let button: HTMLButtonElement = $state()
|
||||
let dialog: Dialog = $state()
|
||||
|
||||
const open = () => dialog.open(button.getBoundingClientRect())
|
||||
</script>
|
||||
|
||||
<button bind:this={button} on:click={open} class="button round">
|
||||
<i class="icon">info</i><slot name="label"></slot>
|
||||
<button bind:this={button} onclick={open} class="button round">
|
||||
<i class="icon">info</i>{@render label?.()}
|
||||
</button>
|
||||
|
||||
<Dialog bind:this={dialog}>
|
||||
<div class="menu">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import { get_misc_vat_rate, get_user } from "lib/PixeldrainAPI";
|
||||
import { countries } from "country-data-list";
|
||||
import PickProvider from "./PickProvider.svelte";
|
||||
|
||||
let state: CheckoutState = {country: null, provider: null, amount: 0, vat: 0, name: ""}
|
||||
let status: CheckoutState = $state({country: null, provider: null, amount: 0, vat: 0, name: ""})
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
@@ -17,21 +17,21 @@ onMount(async () => {
|
||||
// Get the country from the user profile
|
||||
if (user.checkout_country !== "" && countries[user.checkout_country] !== undefined) {
|
||||
const vat_rate = await get_misc_vat_rate(user.checkout_country)
|
||||
state.vat = vat_rate.vat*100
|
||||
state.country = countries[user.checkout_country]
|
||||
status.vat = vat_rate.vat*100
|
||||
status.country = countries[user.checkout_country]
|
||||
}
|
||||
|
||||
// Get the provider from the user profile
|
||||
for (const p of payment_providers) {
|
||||
if (p.name === user.checkout_provider) {
|
||||
state.provider = p
|
||||
status.provider = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get the checkout name from the user profile
|
||||
if (user.checkout_name !== "") {
|
||||
state.name = user.checkout_name
|
||||
status.name = user.checkout_name
|
||||
}
|
||||
}catch (err) {
|
||||
alert("Failed to get user:"+err)
|
||||
@@ -40,45 +40,45 @@ onMount(async () => {
|
||||
</script>
|
||||
|
||||
<div class="highlight_border">
|
||||
{#if state.country !== null}
|
||||
{#if status.country !== null}
|
||||
<div class="navbar">
|
||||
{#if state.country !== null}
|
||||
<button on:click={() => state.country = null}>
|
||||
{#if status.country !== null}
|
||||
<button onclick={() => status.country = null}>
|
||||
Country:
|
||||
<span style="font-size: 1.5em; line-height: 1em;">{state.country.emoji}</span>
|
||||
{state.country.name}
|
||||
<span style="font-size: 1.5em; line-height: 1em;">{status.country.emoji}</span>
|
||||
{status.country.name}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if state.provider !== null}
|
||||
<button on:click={() => state.provider = null}>
|
||||
{#if status.provider !== null}
|
||||
<button onclick={() => status.provider = null}>
|
||||
Provider:
|
||||
<img
|
||||
class="provider_icon"
|
||||
src="/res/img/payment_providers/{state.provider.icon}.svg"
|
||||
alt={state.provider.label}
|
||||
title={state.provider.label}/>
|
||||
{state.provider.label}
|
||||
src="/res/img/payment_providers/{status.provider.icon}.svg"
|
||||
alt={status.provider.label}
|
||||
title={status.provider.label}/>
|
||||
{status.provider.label}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if state.name !== ""}
|
||||
<button on:click={() => state.name = ""}>
|
||||
Name: {state.name}
|
||||
{#if status.name !== ""}
|
||||
<button onclick={() => status.name = ""}>
|
||||
Name: {status.name}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<hr/>
|
||||
{/if}
|
||||
|
||||
{#if state.country === null}
|
||||
<PickCountry bind:state/>
|
||||
{:else if state.provider === null}
|
||||
<PickProvider bind:state/>
|
||||
{:else if state.provider.need_name === true && state.name === ""}
|
||||
<PickName bind:state/>
|
||||
{#if status.country === null}
|
||||
<PickCountry bind:status/>
|
||||
{:else if status.provider === null}
|
||||
<PickProvider bind:status/>
|
||||
{:else if status.provider.need_name === true && status.name === ""}
|
||||
<PickName bind:status/>
|
||||
{:else}
|
||||
<PickAmount bind:state/>
|
||||
<PickAmount bind:status/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@@ -1,27 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { checkout, type CheckoutState } from "./CheckoutLib";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let state: CheckoutState
|
||||
let { status = $bindable() }: {
|
||||
status: CheckoutState;
|
||||
} = $props();
|
||||
|
||||
const amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
|
||||
let amount = 20
|
||||
let amount = $state(20)
|
||||
|
||||
const submit = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
state.amount = amount
|
||||
status.amount = amount
|
||||
loading_start()
|
||||
try {
|
||||
await checkout(state)
|
||||
await checkout(status)
|
||||
}finally {
|
||||
loading_finish()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if state.provider.crypto === true}
|
||||
{#if status.provider.crypto === true}
|
||||
<p style="text-align: initial;" class="highlight_blue">
|
||||
When paying with cryptocurrencies it is important that you pay
|
||||
the <b>exact amount</b> stated on the order. If you pay too
|
||||
@@ -33,11 +36,11 @@ const submit = async (e: SubmitEvent) => {
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<form class="amount_grid" on:submit={submit}>
|
||||
<form class="amount_grid" onsubmit={submit}>
|
||||
<div class="span3">Please choose an amount</div>
|
||||
{#each amounts as a}
|
||||
<button
|
||||
on:click|preventDefault={() => amount = a}
|
||||
onclick={preventDefault(() => amount = a)}
|
||||
class="amount_button"
|
||||
class:button_highlight={amount === a}
|
||||
>
|
||||
@@ -52,7 +55,7 @@ const submit = async (e: SubmitEvent) => {
|
||||
|
||||
<div class="span2" style="text-align: initial;">
|
||||
Total including VAT:
|
||||
<Euro amount={(amount*1e6) + (amount*1e6)*(state.vat/100)}/>
|
||||
<Euro amount={(amount*1e6) + (amount*1e6)*(status.vat/100)}/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button_highlight">
|
||||
|
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
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
|
||||
let country_input = ""
|
||||
let { status = $bindable() }: {
|
||||
status: CheckoutState;
|
||||
} = $props();
|
||||
let country_input = $state("")
|
||||
|
||||
const set_country = async (e?: Event) => {
|
||||
if (e !== undefined) {
|
||||
@@ -21,7 +24,7 @@ const set_country = async (e?: Event) => {
|
||||
// We always clear the provider field after picking a country, as providers
|
||||
// vary per country. If we did not clear the provider then we may end up
|
||||
// with an invalid combination
|
||||
state.provider = null
|
||||
status.provider = null
|
||||
|
||||
loading_start()
|
||||
try {
|
||||
@@ -29,8 +32,8 @@ const set_country = async (e?: Event) => {
|
||||
await put_user({checkout_country: c.alpha3})
|
||||
|
||||
const vat_rate = await get_misc_vat_rate(c.alpha3)
|
||||
state.vat = vat_rate.vat*100
|
||||
state.country = c
|
||||
status.vat = vat_rate.vat*100
|
||||
status.country = c
|
||||
} catch (err) {
|
||||
alert(err)
|
||||
} finally {
|
||||
@@ -56,7 +59,7 @@ const format_country = (c: typeof countries.all[0]) => {
|
||||
Please pick your country of residence
|
||||
</div>
|
||||
<div>
|
||||
<form on:submit|preventDefault={set_country} class="country_form">
|
||||
<form onsubmit={preventDefault(set_country)} class="country_form">
|
||||
<div class="country_search">
|
||||
<datalist id="country_picker">
|
||||
{#each countries.all.filter(c => c.status === "assigned") as c}
|
||||
@@ -74,7 +77,7 @@ const format_country = (c: typeof countries.all[0]) => {
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
<select bind:value={country_input} on:dblclick={set_country} style="padding: 0;" size="10">
|
||||
<select bind:value={country_input} ondblclick={set_country} style="padding: 0;" size="10">
|
||||
{#each countries.all.filter(c => c.status === "assigned") as c}
|
||||
<option value={c.alpha2}>{format_country(c)}</option>
|
||||
{/each}
|
||||
|
@@ -2,8 +2,11 @@
|
||||
import { put_user } from "lib/PixeldrainAPI";
|
||||
import { type CheckoutState } from "./CheckoutLib";
|
||||
|
||||
export let state: CheckoutState
|
||||
let name: string
|
||||
let { status = $bindable() }: {
|
||||
status: CheckoutState;
|
||||
} = $props();
|
||||
|
||||
let name: string = $state()
|
||||
|
||||
const submit = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -14,11 +17,11 @@ const submit = async (e: SubmitEvent) => {
|
||||
alert("Failed to update user:"+ err)
|
||||
}
|
||||
|
||||
state.name = name
|
||||
status.name = name
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="name_form" on:submit={submit}>
|
||||
<form class="name_form" onsubmit={submit}>
|
||||
<div>
|
||||
This payment provider requires a name for checkout. Please enter your
|
||||
full name below
|
||||
|
@@ -2,7 +2,9 @@
|
||||
import { put_user } from "lib/PixeldrainAPI";
|
||||
import { payment_providers, type CheckoutState, type PaymentProvider } from "./CheckoutLib";
|
||||
|
||||
export let state: CheckoutState
|
||||
let { status = $bindable() }: {
|
||||
status: CheckoutState;
|
||||
} = $props();
|
||||
|
||||
const set_provider = async (p: PaymentProvider) => {
|
||||
try {
|
||||
@@ -11,7 +13,7 @@ const set_provider = async (p: PaymentProvider) => {
|
||||
alert("Failed to update user:"+ err)
|
||||
}
|
||||
|
||||
state.provider = p
|
||||
status.provider = p
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -19,8 +21,8 @@ const set_provider = async (p: PaymentProvider) => {
|
||||
|
||||
<div class="providers">
|
||||
{#each payment_providers as p (p.name)}
|
||||
{#if p.country_filter === undefined || p.country_filter.includes(state.country.alpha2)}
|
||||
<button on:click={() => set_provider(p)}>
|
||||
{#if p.country_filter === undefined || p.country_filter.includes(status.country.alpha2)}
|
||||
<button onclick={() => set_provider(p)}>
|
||||
<img src="/res/img/payment_providers/{p.icon}.svg" alt={p.label} title={p.label}/>
|
||||
{p.label}
|
||||
</button>
|
||||
|
@@ -45,7 +45,7 @@ const form_otp: FormConfig = {
|
||||
}
|
||||
|
||||
// The currently rendered form
|
||||
let form: FormConfig = form_login
|
||||
let form: FormConfig = $state(form_login)
|
||||
|
||||
let username = ""
|
||||
let password = ""
|
||||
|
@@ -6,15 +6,15 @@ const finish_login = async () => {
|
||||
location.reload()
|
||||
}
|
||||
|
||||
let page = "login"
|
||||
let page = $state("login")
|
||||
</script>
|
||||
<div class="tab_bar">
|
||||
<button on:click={() => page = "login"} class:button_highlight={page === "login"}>
|
||||
<button onclick={() => page = "login"} class:button_highlight={page === "login"}>
|
||||
<i class="icon small">login</i>
|
||||
<span>Login</span>
|
||||
</button>
|
||||
|
||||
<button on:click={() => page = "register"} class:button_highlight={page === "register"}>
|
||||
<button onclick={() => page = "register"} class:button_highlight={page === "register"}>
|
||||
<i class="icon small">how_to_reg</i>
|
||||
<span>Register</span>
|
||||
</button>
|
||||
|
@@ -49,7 +49,7 @@ onMount(() => {
|
||||
});
|
||||
})
|
||||
|
||||
let stylesheet_2: HTMLLinkElement
|
||||
let stylesheet_2: HTMLLinkElement = $state()
|
||||
const reload_sheet = () => {
|
||||
let stylesheet1 = document.getElementById("stylesheet_theme") as HTMLLinkElement
|
||||
|
||||
|
@@ -5,8 +5,8 @@ import { formatDataVolume, formatDataVolumeBits } from "util/Formatting";
|
||||
import ProgressBar from "util/ProgressBar.svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
|
||||
let running = false
|
||||
let data_received = 0
|
||||
let running = $state(false)
|
||||
let data_received = $state(0)
|
||||
const update_interval = 100
|
||||
const start = async (dur = 10000) => {
|
||||
if (running) {
|
||||
@@ -39,7 +39,7 @@ const start = async (dur = 10000) => {
|
||||
running = false
|
||||
}
|
||||
|
||||
let latency = 0
|
||||
let latency = $state(0)
|
||||
const measure_latency = async () => {
|
||||
// We request one byte from the server ten times. If the latency of one
|
||||
// request is lower than the last one then that latency is saved in the
|
||||
@@ -68,12 +68,12 @@ const measure_latency = async () => {
|
||||
}
|
||||
|
||||
// Average speed for the whole test
|
||||
let speed = 0
|
||||
let result_link = ""
|
||||
let server = ""
|
||||
let speed = $state(0)
|
||||
let result_link = $state("")
|
||||
let server = $state("")
|
||||
|
||||
let progress_duration = 0
|
||||
let progress_unchanged = 0
|
||||
let progress_duration = $state(0)
|
||||
let progress_unchanged = $state(0)
|
||||
|
||||
const measure_speed = (stop, test_duration) => {
|
||||
speed = 0
|
||||
|
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
let loaded = false
|
||||
let rows = []
|
||||
let loaded = $state(false)
|
||||
let rows = $state([])
|
||||
|
||||
const load_keys = async () => {
|
||||
loading_start()
|
||||
@@ -93,14 +94,14 @@ const logout = async (key) => {
|
||||
full control over your account. Do not show your API keys to
|
||||
someone or something you don't trust!
|
||||
</p>
|
||||
<button class="button_red" on:click={load_keys}>
|
||||
<button class="button_red" onclick={load_keys}>
|
||||
<i class="icon">lock_open</i> Show API keys
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={create_key}>
|
||||
<button onclick={create_key}>
|
||||
<i class="icon">add</i> Create new API key
|
||||
</button>
|
||||
</div>
|
||||
@@ -134,7 +135,7 @@ const logout = async (key) => {
|
||||
<td>{formatDate(row.last_used_time, true, true, false)}</td>
|
||||
<td>{row.creation_ip_address}</td>
|
||||
<td>
|
||||
<button on:click|preventDefault={() => {logout(row.auth_key)}} class="button button_red round">
|
||||
<button onclick={preventDefault(() => {logout(row.auth_key)})} class="button button_red round">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</td>
|
||||
|
@@ -7,7 +7,7 @@ import OtpSetup from "./OTPSetup.svelte";
|
||||
import { put_user } from "lib/PixeldrainAPI";
|
||||
|
||||
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
|
||||
let affiliate_deny = false
|
||||
let affiliate_deny = $state(false)
|
||||
onMount(() => {
|
||||
affiliate_deny = localStorage.getItem("affiliate_deny") === "1"
|
||||
})
|
||||
|
@@ -5,8 +5,8 @@ import { formatDate } from "util/Formatting";
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
let month_str = ""
|
||||
let data = []
|
||||
let month_str = $state("")
|
||||
let data = $state([])
|
||||
|
||||
const load_activity = async () => {
|
||||
loading_start()
|
||||
@@ -66,12 +66,12 @@ onMount(() => {
|
||||
|
||||
<h3>{month_str}</h3>
|
||||
<div class="toolbar">
|
||||
<button on:click={last_month}>
|
||||
<button onclick={last_month}>
|
||||
<i class="icon">chevron_left</i>
|
||||
Previous month
|
||||
</button>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={next_month}>
|
||||
<button onclick={next_month}>
|
||||
Next month
|
||||
<i class="icon">chevron_right</i>
|
||||
</button>
|
||||
@@ -118,12 +118,12 @@ onMount(() => {
|
||||
|
||||
{#if data.length > 100}
|
||||
<div class="toolbar">
|
||||
<button on:click={last_month}>
|
||||
<button onclick={last_month}>
|
||||
<i class="icon">chevron_left</i>
|
||||
Previous month
|
||||
</button>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={next_month}>
|
||||
<button onclick={next_month}>
|
||||
Next month
|
||||
<i class="icon">chevron_right</i>
|
||||
</button>
|
||||
|
@@ -4,11 +4,14 @@ import Modal from "util/Modal.svelte";
|
||||
import { get_user, put_user } from "lib/PixeldrainAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let { always = false }: {
|
||||
// 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 referral: string
|
||||
always?: boolean;
|
||||
} = $props();
|
||||
|
||||
let modal: Modal = $state()
|
||||
let referral: string = $state()
|
||||
let shown = false
|
||||
|
||||
export const prompt = async (ref: string) => {
|
||||
@@ -105,10 +108,10 @@ const deny = () => {
|
||||
choose 'Deny' then we will never show this pop-up again.
|
||||
</p>
|
||||
<div class="buttons">
|
||||
<button class="button button_red" on:click={e => deny()}>
|
||||
<button class="button button_red" onclick={e => deny()}>
|
||||
Deny
|
||||
</button>
|
||||
<button class="button button_highlight" on:click={e => allow()}>
|
||||
<button class="button button_highlight" onclick={e => allow()}>
|
||||
Accept
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import Pro from "icons/Pro.svelte";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
@@ -6,10 +7,10 @@ import ProgressBar from "util/ProgressBar.svelte";
|
||||
import SuccessMessage from "util/SuccessMessage.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
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
|
||||
let success_message = $state()
|
||||
let hotlinking = $state(window.user.hotlinking_enabled)
|
||||
let transfer_cap = $state(window.user.monthly_transfer_cap / 1e12)
|
||||
let skip_viewer = $state(window.user.skip_file_viewer)
|
||||
|
||||
const update = async () => {
|
||||
const form = new FormData()
|
||||
@@ -44,7 +45,7 @@ let toggle_hotlinking = () => {
|
||||
update()
|
||||
}
|
||||
|
||||
let transfer_used = 0
|
||||
let transfer_used = $state(0)
|
||||
let load_transfer_used = () => {
|
||||
let today = new Date()
|
||||
let start = new Date()
|
||||
@@ -80,7 +81,7 @@ onMount(() => {
|
||||
<h2><Pro/>Hotlinking (bandwidth sharing)</h2>
|
||||
<SuccessMessage bind:this={success_message}></SuccessMessage>
|
||||
|
||||
<button on:click={toggle_hotlinking}>
|
||||
<button onclick={toggle_hotlinking}>
|
||||
{#if hotlinking}
|
||||
<i class="icon green">check</i> ON (click to turn off)
|
||||
{:else}
|
||||
@@ -101,7 +102,7 @@ onMount(() => {
|
||||
Billshock limit in terabytes per month (1 TB = 1000 GB). Set to 0 to
|
||||
disable.
|
||||
</p>
|
||||
<form on:submit|preventDefault={update} class="billshock_container">
|
||||
<form onsubmit={preventDefault(update)} class="billshock_container">
|
||||
<input type="number" bind:value={transfer_cap} step="0.1" min="0" style="width: 5em;"/>
|
||||
<div style="margin: 0.5em;">TB</div>
|
||||
<button type="submit">
|
||||
@@ -123,7 +124,7 @@ onMount(() => {
|
||||
</p>
|
||||
|
||||
<h2><Pro/>Skip download page</h2>
|
||||
<button on:click={toggle_skip_viewer}>
|
||||
<button onclick={toggle_skip_viewer}>
|
||||
{#if skip_viewer}
|
||||
<i class="icon green">check</i> ON (click to turn off)
|
||||
{:else}
|
||||
|
@@ -3,8 +3,8 @@ import { onMount } from "svelte";
|
||||
import CopyButton from "layout/CopyButton.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let app_name = ""
|
||||
let api_key = ""
|
||||
let app_name = $state("")
|
||||
let api_key = $state("")
|
||||
const create_key = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
@@ -30,7 +30,7 @@ const create_key = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let show_key = ""
|
||||
let show_key = $state("")
|
||||
const toggle_show_key = () => {
|
||||
if (show_key === "") {
|
||||
show_key = api_key
|
||||
@@ -90,7 +90,7 @@ onMount(() => {
|
||||
|
||||
{#if !api_key}
|
||||
<div class="center">
|
||||
<button class="button_highlight" on:click={create_key}>
|
||||
<button class="button_highlight" onclick={create_key}>
|
||||
<i class="icon">add</i>
|
||||
Generate key
|
||||
</button>
|
||||
@@ -100,7 +100,7 @@ onMount(() => {
|
||||
|
||||
<div class="copy_container">
|
||||
<CopyButton text={api_key}>Copy key to clipboard</CopyButton>
|
||||
<button on:click={toggle_show_key} class="copy_button" class:button_highlight={show_key !== ""}>
|
||||
<button onclick={toggle_show_key} class="copy_button" class:button_highlight={show_key !== ""}>
|
||||
<i class="icon">visibility</i>
|
||||
{#if show_key === ""}
|
||||
Show key
|
||||
|
@@ -37,8 +37,8 @@ const checkout = async (network = "", amount = 0, country = "") => {
|
||||
}
|
||||
}
|
||||
|
||||
let invoices = []
|
||||
let unpaid_invoice = 0
|
||||
let invoices = $state([])
|
||||
let unpaid_invoice = $state(0)
|
||||
const load_invoices = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
|
@@ -1,13 +1,14 @@
|
||||
<script>
|
||||
import { preventDefault } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import Persistence from "icons/Persistence.svelte";
|
||||
import SuccessMessage from "util/SuccessMessage.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let success_message
|
||||
let success_message = $state()
|
||||
|
||||
// Embedding settings
|
||||
let embed_domains = ""
|
||||
let embed_domains = $state("")
|
||||
|
||||
const save_embed = async () => {
|
||||
const form = new FormData()
|
||||
@@ -63,7 +64,7 @@ onMount(() => {
|
||||
space. Like this: 'pixeldrain.com google.com twitter.com'
|
||||
</p>
|
||||
Domain names:<br/>
|
||||
<form class="form_row" on:submit|preventDefault={save_embed}>
|
||||
<form class="form_row" onsubmit={preventDefault(save_embed)}>
|
||||
<input class="grow" bind:value={embed_domains} type="text"/>
|
||||
<button class="shrink" action="submit"><i class="icon">save</i> Save</button>
|
||||
</form>
|
||||
|
@@ -1,11 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import ProgressBar from "util/ProgressBar.svelte";
|
||||
import { formatDataVolume } from "util/Formatting"
|
||||
import { user } from "lib/UserStore";
|
||||
|
||||
export let total = 0
|
||||
export let used = 0
|
||||
let { total = 0, used = 0 }: {
|
||||
total?: number;
|
||||
used?: number;
|
||||
} = $props();
|
||||
|
||||
$: frac = used / total
|
||||
let frac = $derived(used / total)
|
||||
</script>
|
||||
|
||||
<ProgressBar total={total} used={used}></ProgressBar>
|
||||
@@ -31,7 +34,7 @@ $: frac = used / total
|
||||
files have a daily download limit and hotlinking is disabled.
|
||||
</p>
|
||||
|
||||
{#if window.user.monthly_transfer_cap > 0}
|
||||
{#if $user.monthly_transfer_cap > 0}
|
||||
<p>
|
||||
You have a billshock limit configured. <a
|
||||
href="/user/sharing/bandwidth">increase or disable the limit</a> to
|
||||
@@ -53,7 +56,7 @@ $: frac = used / total
|
||||
and hotlinking is disabled.
|
||||
</p>
|
||||
|
||||
{#if window.user.monthly_transfer_cap > 0}
|
||||
{#if $user.monthly_transfer_cap > 0}
|
||||
<p>
|
||||
You have a billshock limit configured. <a
|
||||
href="/user/sharing/bandwidth">increase or disable the limit</a> to
|
||||
|
@@ -5,11 +5,11 @@ import CopyButton from "layout/CopyButton.svelte";
|
||||
import ToggleButton from "layout/ToggleButton.svelte";
|
||||
import { check_response, get_endpoint, get_user, type User } from "lib/PixeldrainAPI";
|
||||
|
||||
let user: User = null
|
||||
let secret = ""
|
||||
let uri = ""
|
||||
let qr = ""
|
||||
let reveal_key = false
|
||||
let user: User = $state(null)
|
||||
let secret = $state("")
|
||||
let uri = $state("")
|
||||
let qr = $state("")
|
||||
let reveal_key = $state(false)
|
||||
const generate_key = async () => {
|
||||
try {
|
||||
let form = new FormData()
|
||||
@@ -28,7 +28,7 @@ const generate_key = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let otp = ""
|
||||
let otp = $state("")
|
||||
const verify = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -133,7 +133,7 @@ onMount(async () => {
|
||||
app is working properly. This step enables two-factor authentication
|
||||
on your account.
|
||||
</p>
|
||||
<form id="otp_verify" on:submit={verify} class="otp_form">
|
||||
<form id="otp_verify" onsubmit={verify} class="otp_form">
|
||||
<input bind:value={otp} type="text" autocomplete="one-time-code" pattern={"[0-9]{6}"} required>
|
||||
<Button form="otp_verify" label="Verify OTP"/>
|
||||
</form>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let patreon_result = ""
|
||||
let patreon_message = ""
|
||||
let patreon_error = ""
|
||||
let patreon_result = $state("")
|
||||
let patreon_message = $state("")
|
||||
let patreon_error = $state("")
|
||||
|
||||
onMount(() => {
|
||||
if (window.location.hash.startsWith("#")) {
|
||||
@@ -57,7 +57,7 @@ onMount(() => {
|
||||
cause issues with the authentication process. Try linking your
|
||||
subscription in <a
|
||||
href="https://www.mozilla.org/firefox/">Firefox</a> for example.
|
||||
<p/>
|
||||
</p>
|
||||
<p>
|
||||
If the information above does not solve your issue then please
|
||||
contact me on <a
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user