Update to svelte 5

This commit is contained in:
2025-10-13 16:05:50 +02:00
parent f936e4c0f2
commit 6d89c5ddd9
129 changed files with 2443 additions and 2180 deletions

1710
svelte/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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 = []

View File

@@ -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>

View File

@@ -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/>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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}

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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}/>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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"}

View File

@@ -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()

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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 {

View File

@@ -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

View File

@@ -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}/>

View File

@@ -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

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 = () => {

View File

@@ -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>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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))

View File

@@ -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)}

View File

@@ -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

View File

@@ -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">

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,5 +1,7 @@
<script lang="ts">
export let title = ""
let { title = "" }: {
title?: string;
} = $props();
</script>
<h1>{title}</h1>

View File

@@ -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")

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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}

View File

@@ -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

View File

@@ -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>

View File

@@ -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 = ""

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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"
})

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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