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