Fork pd_web, remove everything we don't need

This commit is contained in:
2025-09-24 15:37:57 +02:00
parent 9dcdd94b3a
commit fd5cd0bfd1
415 changed files with 146269 additions and 120786 deletions

View File

@@ -196,9 +196,9 @@ import OtherPlans from "./OtherPlans.svelte";
</div>
<div class="feature_cell prepaid_feat">
<span class="bold">PayPal</span>,
<span class="bold">Credit/debit</span>,
<span class="bold">iDEAL</span><br/>
And many regional providers
<span class="bold">Bitcoin</span>,
<span class="bold">Litecoin</span>,
<span class="bold">Monero</span>
</div>
<div></div>

View File

@@ -20,50 +20,38 @@ let upload_widget
<AddressReputation/>
<div class="page_content">
{#if window.user && window.user.username && window.user.username !== ""}
<div
class="drop_target"
use:drop_target={{
upload: (files) => upload_widget.upload_files(files),
shadow: "var(--highlight_color) 0 0 10px 2px inset",
}}
>
<UploadWidget bind:this={upload_widget}/>
</div>
{:else}
<section>
<p>
Pixeldrain offers services for efficiently moving and storing
digital files on the internet.
</p>
<h2>What pixeldrain is good at</h2>
<ul>
<li>
Serving large files to millions of people worldwide
</li>
<li>
Storing files for less money than all the competition
</li>
</ul>
<h2>Things we take very seriously</h2>
<ul>
<li>
<b>Performance</b> - Slow software is a waste of time. We
don't want to make you wait, so pixeldrain is completely
tuned for maximum performance
</li>
<li>
<b>Privacy</b> - There is too much tracking on the web
nowadays. Pixeldrain goes in the other direction, this site
does not contain any advertisements or third party tracking
scripts
</li>
<li>
Bullet lists
</li>
</ul>
</section>
{/if}
<section>
<p>
Pixeldrain offers services for efficiently moving and storing
digital files on the internet.
</p>
<h2>What pixeldrain is good at</h2>
<ul>
<li>
Serving large files to millions of people worldwide
</li>
<li>
Storing files for less money than all the competition
</li>
</ul>
<h2>Things we take very seriously</h2>
<ul>
<li>
<b>Performance</b> - Slow software is a waste of time. We
don't want to make you wait, so pixeldrain is completely
tuned for maximum performance
</li>
<li>
<b>Privacy</b> - There is too much tracking on the web
nowadays. Pixeldrain goes in the other direction, this site
does not contain any advertisements or third party tracking
scripts
</li>
<li>
Bullet lists
</li>
</ul>
</section>
</div>
<header>
@@ -211,9 +199,6 @@ header > span {
max-width: 100%;
border-radius: 12px;
}
.drop_target {
border-radius: 8px;
}
.bold {
font-weight: bold;
color: var(--highlight_color);

View File

@@ -0,0 +1,172 @@
<script>
import { onMount } from "svelte";
import Euro from "util/Euro.svelte";
import ProgressBar from "util/ProgressBar.svelte";
let pixeldrain_storage = 0
let pixeldrain_egress = 0
let pixeldrain_total = 0
let backblaze_storage = 0
let backblaze_egress = 0
let backblaze_api = 0
let backblaze_total = 0
let wasabi_storage = 0
let wasabi_total = 0
let price_amazon = 0
let price_azure = 0
let price_google = 0
let price_max = 0
let storage = 10 // TB
let egress = 10 // TB
let avg_file_size = 1000 // kB
$: {
pixeldrain_storage = storage * 4
pixeldrain_egress = egress * 1
pixeldrain_total = pixeldrain_storage + pixeldrain_egress
// Egress at Backblaze is free up to three times the amount of storage, then
// it's $10/TB
backblaze_storage = storage * 6
backblaze_egress = Math.max(egress - (storage * 3), 0) * 10
backblaze_api = ((egress * 1e12) / (avg_file_size * 1e3)) * 0.0000004
backblaze_total = backblaze_storage + backblaze_egress + backblaze_api
// Wasabi does not have egress fees
wasabi_storage = storage * 6.99
wasabi_total = (egress > storage) ? 0 : wasabi_storage
// price_amazon = (storage * 26) + (egress * 90)
// price_azure = (storage * 20) + (egress * 80)
// price_google = (storage * 20) + (egress * 20)
price_max = Math.max(pixeldrain_total, backblaze_total, wasabi_total, price_amazon, price_azure, price_google)
}
onMount(() => {})
</script>
<h2>Price calculator</h2>
<div class="inputs">
<div>
<div>Storage</div>
<div><input type="number" bind:value={storage}> TB / month</div>
</div>
<div>
<div>Egress</div>
<div><input type="number" bind:value={egress}> TB / month</div>
</div>
<div>
<div>Average file size</div>
<div><input type="number" bind:value={avg_file_size}> kB</div>
</div>
</div>
<div class="bars">
<div>
<div>
Pixeldrain - <Euro amount={pixeldrain_total*1e6}/> / month<br/>
<Euro amount={pixeldrain_storage*1e6}/> storage,
<Euro amount={pixeldrain_egress*1e6}/> egress
<ProgressBar used={pixeldrain_total} total={price_max}/>
</div>
</div>
<div>
<div>
Backblaze B2 - <Euro symbol="$" amount={backblaze_total*1e6}/> / month<br/>
<Euro symbol="$" amount={backblaze_storage*1e6}/> storage,
<Euro symbol="$" amount={backblaze_egress*1e6}/> egress,
<Euro symbol="$" amount={backblaze_api*1e6}/> API calls
<ProgressBar used={backblaze_total} total={price_max}/>
</div>
</div>
<div>
<div>
Wasabi - <Euro symbol="$" amount={wasabi_total*1e6}/> / month<br/>
<Euro symbol="$" amount={wasabi_storage*1e6}/> storage
<ProgressBar used={wasabi_total} total={price_max}/>
</div>
{#if egress > storage}
<div>
Wasabi does not allow users to download more than their amount
of stored data per month. For this reason Wasabi is excluded
from price calculations when egress is higher than storage.
</div>
{/if}
</div>
<!-- <div>
<div>
Amazon S3: <Euro symbol="$" amount={price_amazon*1e6}/> / month
<ProgressBar used={price_amazon} total={price_max}/>
</div>
<div>
Amazon's pricing is too complicated to accurately represent here.
</div>
</div>
<div>
<div>
Microsoft Azure: <Euro symbol="$" amount={price_azure*1e6}/> / month
<ProgressBar used={price_azure} total={price_max}/>
</div>
<div>
Azure's pricing is too complicated to accurately represent here.
</div>
</div>
<div>
<div>
Google: <Euro symbol="$" amount={price_google*1e6}/> / month
<ProgressBar used={price_google} total={price_max}/>
</div>
<div>
Google's pricing is too complicated to accurately represent here.
</div>
</div> -->
</div>
<p>
Note that while pixeldrain might not seem to be the cheapest option in some
cases, most cloud providers have extra hidden costs for API calls and
region-specific prices. This makes it very hard to accurately compare the
pricing of these platforms. Pixeldrain includes no hidden costs, I only
charge for storage and egress.
</p>
<style>
.inputs {
display: flex;
flex-direction: row;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
.inputs > div {
flex: 1 1 auto;
display: flex;
flex-direction: column;
border-radius: 6px;
overflow: hidden;
border: 2px solid var(--card_color);
}
.bars {
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
}
.bars > div {
flex: 1 1 auto;
display: flex;
flex-direction: column;
border-radius: 6px;
overflow: hidden;
border: 2px solid var(--card_color);
}
.bars > div > div {
padding: 4px;
}
.bars > div > div:first-child {
background: var(--card_color);
}
</style>

View File

@@ -1,304 +0,0 @@
<script>
import { add_upload_history, domain_url } from "util/Util.svelte"
import { formatDataVolume, formatDuration } from "util/Formatting"
import Spinner from "util/Spinner.svelte";
export let job = {}
let file_button
let progress_bar
let tries = 0
let start_time = 0
let remaining_time = 0
let last_update_time = 0
let progress = 0
let transfer_rate = 0
const on_progress = (loaded, total) => {
job.loaded_size = loaded
job.total_size = total
if (last_update_time === 0) {
last_update_time = new Date().getTime()
return
}
let current_time = new Date().getTime()
last_update_time = current_time
let elapsed_time = current_time - start_time
remaining_time = (elapsed_time/progress) - elapsed_time
progress = job.loaded_size / job.total_size
// Calculate transfer rate by dividing the total uploaded size by the total
// running time
transfer_rate = Math.floor(job.loaded_size / ((current_time - start_time) / 1000))
progress_bar.style.width = (progress * 100) + "%"
if (progress >= 1) {
job.status = "processing"
progress_bar.style.opacity = "0"
} else {
progress_bar.style.opacity = "1"
}
}
let href = null
let target = null
const on_success = (resp) => {
transfer_rate = 0
job.loaded_size = job.total_size
job.file = null // Delete reference to file to free memory
job.id = resp.id
job.status = "finished"
job.on_finished(job)
add_upload_history(resp.id)
href = "/u/"+resp.id
target = "_blank"
progress_bar.style.width = "100%"
progress_bar.style.opacity = "0"
}
let error_id = ""
let error_reason = ""
const on_failure = (status, message) => {
transfer_rate = 0
job.loaded_size = job.total_size
job.file = null // Delete reference to file to free memory
error_id = status
error_reason = message
job.status = "error"
file_button.style.background = 'var(--danger_color)'
file_button.style.color = 'var(--highlight_text_color)'
progress_bar.style.width = "0"
job.on_finished(job)
}
export const start = () => {
job.status = "uploading"
// Check the file size limit. For free accounts it's 20 GB
if (window.user.subscription.file_size_limit === 0) {
window.user.subscription.file_size_limit = 20e9
}
if (job.total_size > window.user.subscription.file_size_limit) {
on_failure(
"file_too_large",
"This file is too large. Check out the Pro subscription to increase the file size limit"
)
return
}
start_time = new Date().getTime()
let xhr = new XMLHttpRequest();
xhr.open("PUT", window.api_endpoint+"/file/"+encodeURIComponent(job.name), true);
xhr.timeout = 86400000; // 24 hours, to account for slow connections
xhr.upload.addEventListener("progress", evt => {
if (evt.lengthComputable) {
on_progress(evt.loaded, evt.total)
}
});
xhr.onreadystatechange = () => {
// readystate 4 means the upload is done
if (xhr.readyState !== 4) {
return
}
if (xhr.status >= 100 && xhr.status < 400) {
// Request is a success
on_success(JSON.parse(xhr.response))
} else if (xhr.status >= 400) {
// Request failed
console.log("Upload error. status: " + xhr.status + " response: " + xhr.response);
let resp = {}
if (xhr.status === 429) {
resp = {
value: "too_many_requests",
message: "Too many requests. Please wait a few seconds",
}
} else {
resp = JSON.parse(xhr.response)
}
if (resp.value == "file_too_large"
|| resp.value == "ip_banned"
|| resp.value == "user_out_of_space"
|| tries === 3) {
// Permanent failure
on_failure(resp.value, resp.message)
} else {
// Temporary failure, try again in 5 seconds
tries++
setTimeout(start, 5000)
}
} else if (xhr.status === 0) {
on_failure("request_failed", "The connection was interrupted")
} else {
// Request did not arrive
if (tries < 3) {
// Try again
tries++
setTimeout(start, 5000)
} else {
// Give up after three tries
on_failure(xhr.responseText, xhr.responseText)
}
}
};
xhr.send(job.file);
}
</script>
<a bind:this={file_button} class="upload_task" {href} {target}>
<div class="top_half">
<div class="thumbnail">
{#if job.status === "queued"}
<i class="icon">cloud_queue</i>
{:else if job.status === "uploading"}
<i class="icon">cloud_upload</i>
{:else if job.status === "processing"}
<Spinner></Spinner>
{:else if job.status === "finished"}
<img src="/api/file/{job.id}/thumbnail" alt="file thumbnail" />
{:else if job.status === "error"}
<i class="icon">error</i>
{/if}
</div>
<div class="queue_body">
<div class="title">
{#if job.status === "error"}
{error_reason}
{:else}
{job.name}
{/if}
</div>
<div class="stats">
{#if job.status === "queued"}
Queued...
{:else if job.status === "uploading"}
<div class="stat">
{(progress*100).toPrecision(3)}%
</div>
<div class="stat">
ETA {formatDuration(remaining_time, 0)}
</div>
<div class="stat">
{formatDataVolume(transfer_rate, 3)}/s
</div>
{:else if job.status === "processing"}
Calculating parity data...
{:else if job.status === "finished"}
<span class="file_link">
{domain_url() + "/u/" + job.id}
</span>
{:else if job.status === "error"}
{error_id}
{/if}
</div>
</div>
</div>
<div class="progress">
<div bind:this={progress_bar} class="progress_bar"></div>
</div>
</a>
<style>
.upload_task{
position: relative;
width: 440px;
max-width: 95%;
height: 4em;
margin: 6px;
padding: 0;
overflow: hidden;
border-radius: 6px;
background: var(--input_background);
color: var(--body_text_color);
word-break: break-all;
text-align: left;
line-height: 1.2em;
display: inline-flex;
flex-direction: column;
transition: background 0.2s, opacity 2s;
white-space: normal;
text-overflow: ellipsis;
text-decoration: none;
vertical-align: top;
cursor: pointer;
}
.top_half {
flex: 1 1 auto;
display: flex;
flex-direction: row;
overflow: hidden;
}
.upload_task:hover {
background: var(--input_hover_background);
text-decoration: none;
}
.thumbnail {
display: flex;
flex: 0 0 auto;
width: 4em;
margin-right: 4px;
align-items: center;
justify-content: center;
}
.thumbnail > img {
width: 90%;
border-radius: 4px;
}
.thumbnail > i {
font-size: 3em;
}
.queue_body {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.queue_body > .title {
flex: 1 1 auto;
overflow: hidden;
}
.queue_body > .stats {
flex: 0 0 auto;
display: flex;
flex-direction: row;
height: 1.4em;
border-top: 1px solid var(--separator);
text-align: center;
font-family: sans-serif, monospace;
font-size: 0.9em;
}
.queue_body > .stats > .stat {
flex: 0 1 100%;
}
.file_link{
color: var(--highlight_color);
}
.progress {
flex: 0 0 auto;
height: 3px;
}
.progress_bar {
background: var(--highlight_background);
height: 100%;
width: 0;
transition: width 0.25s, opacity 3s;
transition-timing-function: linear, ease;
}
</style>

View File

@@ -1,126 +0,0 @@
<script>
import { formatDataVolume, formatDuration } from "util/Formatting";
import ProgressBar from "util/ProgressBar.svelte";
export let upload_queue = []
let stats_interval = null
let stats_interval_ms = 1000
let finished = false
export const start = () => {
if (stats_interval === null) {
start_time = new Date().getTime()
stats_interval = setInterval(stats_update, stats_interval_ms)
}
finished = false
}
export const finish = () => {
if (stats_interval !== null) {
clearInterval(stats_interval)
stats_interval = null
}
finished = true
start_time = 0
total_loaded = total_size
previously_loaded = total_size
total_progress = 1
total_rate = 0
document.title = "Finished! ~ pixeldrain"
}
let start_time = 0
let total_progress = 0
let total_size = 0
let total_loaded = 0
let previously_loaded = 0
let last_total_loaded = 0
let total_rate = 0
let elapsed_time = 0
let remaining_time = 0
const stats_update = () => {
if (start_time === 0) {
start_time = new Date().getTime()
}
// Get total size of upload queue and size of finished uploads
total_size = 0
total_loaded = 0
for (let i = 0; i < upload_queue.length; i++) {
total_size += upload_queue[i].total_size
total_loaded += upload_queue[i].loaded_size
}
total_progress = (total_loaded - previously_loaded) / (total_size - previously_loaded)
// Calculate ETA by estimating the total time and subtracting the elapsed time
elapsed_time = new Date().getTime() - start_time
remaining_time = (elapsed_time/total_progress) - elapsed_time
// Calculate the rate by comparing the current progress with the last iteration
total_rate = Math.floor(
(total_rate * 0.8) +
(((1000 / stats_interval_ms) * (total_loaded - last_total_loaded)) * 0.2)
)
last_total_loaded = total_loaded
document.title = (total_progress*100).toFixed(0) + "% ~ " +
formatDuration(remaining_time, 0) +
" ~ uploading to pixeldrain"
}
</script>
<div class="stats_box">
<div>
<div>
Size {formatDataVolume(total_size, 3)}
</div>
<div>
Progress {(total_progress*100).toPrecision(3)}%
</div>
</div>
<div>
{#if finished}
<div>
Time {formatDuration(elapsed_time, 0)}
</div>
<div>
Rate {formatDataVolume(total_loaded / (elapsed_time/1000), 3)}/s
</div>
{:else}
<div>
ETA {formatDuration(remaining_time, 0)}
</div>
<div>
Rate {formatDataVolume(total_rate, 3)}/s
</div>
{/if}
</div>
</div>
<ProgressBar total={total_size} used={total_loaded} animation="linear" speed={stats_interval_ms}/>
<style>
.stats_box {
display: inline-flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
text-align: center;
}
.stats_box > div {
flex: 1 1 auto;
display: flex;
flex-direction: row;
}
.stats_box > div > div {
flex: 1 1 auto;
min-width: 150px;
}
</style>

View File

@@ -1,478 +0,0 @@
<script>
import UploadProgressBar from "./UploadProgressBar.svelte"
import { domain_url } from "util/Util.svelte"
import { tick } from "svelte"
import Facebook from "icons/Facebook.svelte"
import Reddit from "icons/Reddit.svelte"
import Twitter from "icons/Twitter.svelte"
import Tumblr from "icons/Tumblr.svelte"
import StorageProgressBar from "user_home/StorageProgressBar.svelte"
import Konami from "util/Konami.svelte"
import UploadStats from "./UploadStats.svelte";
import CopyButton from "layout/CopyButton.svelte";
// === UPLOAD LOGIC ===
let file_input_field
const file_input_change = (event) => {
// Start uploading the files async
upload_files(event.target.files)
// This resets the file input field
file_input_field.nodeValue = ""
}
const paste = (e) => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
upload_files(e.clipboardData.files)
}
}
let active_uploads = 0
let upload_queue = []
let state = "idle" // idle, uploading, finished
let upload_stats
export const upload_files = async (files) => {
if (files.length === 0) {
return
}
// Add files to the queue
for (let i = 0; i < files.length; i++) {
if (files[i].type === "" && files[i].size === 0) {
continue
}
upload_queue.push({
file: files[i],
name: files[i].name,
status: "queued",
component: null,
id: "",
total_size: files[i].size,
loaded_size: 0,
on_finished: finish_upload,
})
}
// Reassign array and wait for tick to complete. After the tick is completed
// each upload progress bar will have bound itself to its array item
upload_queue = upload_queue
await tick()
start_upload()
}
const start_upload = () => {
let finished_count = 0
for (let i = 0; i < upload_queue.length && active_uploads < 3; i++) {
if (upload_queue[i].status == "queued") {
active_uploads++
upload_queue[i].component.start()
} else if (
upload_queue[i].status == "finished" ||
upload_queue[i].status == "error"
) {
finished_count++
}
}
if (active_uploads === 0 && finished_count != 0) {
state = "finished"
upload_stats.finish()
uploads_finished()
} else {
state = "uploading"
upload_stats.start()
}
}
const finish_upload = (file) => {
active_uploads--
start_upload()
}
const leave_confirmation = e => {
if (state === "uploading") {
e.preventDefault()
e.returnValue = "If you close the page your files will stop uploading. Do you want to continue?"
return e.returnValue
} else {
return null
}
}
// === SHARING BUTTONS ===
let navigator_share = !!(window.navigator && window.navigator.share)
let share_title = ""
let share_link = ""
let input_album_name = ""
let btn_upload_text
let btn_copy_link
let btn_open_link
let btn_show_qr
let btn_share_email
let btn_share_twitter
let btn_share_facebook
let btn_share_reddit
let btn_share_tumblr
let btn_create_list
let btn_copy_links
let btn_copy_markdown
let btn_copy_bbcode
const uploads_finished = async () => {
let count = upload_queue.reduce(
(acc, curr) => curr.status === "finished" ? acc + 1 : acc, 0,
)
if (count === 1) {
share_title = "Download " + upload_queue[0].name + " here"
share_link = domain_url() + "/u/" + upload_queue[0].id
} else if (count > 1) {
try {
const resp = await create_list(count+" files", true)
console.log("Automatic list ID " + resp.id)
share_title = "View a collection of "+count+" files here"
share_link = domain_url() + "/l/" + resp.id
} catch (err) {
alert("Failed to generate link. Please check your internet connection and try again.\nError: " + err)
}
}
generate_link_list()
}
async function create_list(title, anonymous) {
let files = upload_queue.reduce(
(acc, curr) => {
if (curr.status === "finished") {
acc.push({"id": curr.id})
}
return acc
},
[],
)
const resp = await fetch(
window.api_endpoint+"/list",
{
method: "POST",
headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify({
"title": title,
"anonymous": anonymous,
"files": files
})
}
)
if(!resp.ok) {
return Promise.reject("HTTP error: "+resp.status)
}
return await resp.json()
}
let qr_visible = false
const open_link = () => window.open(share_link, "_blank")
const show_qr_code = () => qr_visible = !qr_visible
const share_mail = () => window.open("mailto:please@set.address?subject=File%20on%20pixeldrain&body=" + share_link)
const share_twitter = () => window.open("https://twitter.com/share?url=" + share_link)
const share_facebook = () => window.open('https://www.facebook.com/sharer.php?u=' + share_link)
const share_reddit = () => window.open('https://www.reddit.com/submit?url=' + share_link)
const share_tumblr = () => window.open('https://www.tumblr.com/share/link?url=' + share_link)
const share_navigator = () => {
window.navigator.share({ title: "Pixeldrain", text: share_title, url: share_link })
}
const create_album = () => {
if (!input_album_name) {
return
}
create_list(input_album_name, false).then(resp => {
window.location = '/l/' + resp.id
}).catch(err => {
alert("Failed to create list. Server says this:\n"+err)
})
}
const get_finished_files = () => {
return upload_queue.reduce(
(acc, curr) => {
if (curr.status === "finished") {
acc.push(curr)
}
return acc
},
[],
)
}
let link_list = ""
let bbcode = ""
let markdown = ""
const generate_link_list = () => {
// Add the text to the textarea
link_list = ""
bbcode = ""
markdown = ""
let files = get_finished_files()
files.forEach(file => {
// Link list example: https://pixeldrain.com/u/abcd1234 Some_file.png
link_list += domain_url() + "/u/" + file.id + " " + file.name + "\n"
// BBCode example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
bbcode += "[url=" + domain_url() + "/u/" + file.id + "]" + file.name + "[/url]\n"
// Markdown example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (files.length > 1) {
markdown += " * "
}
markdown += "[" + file.name + "](" + domain_url() + "/u/" + file.id + ")\n"
})
if (share_link.includes("/l/")) {
link_list += "\n" + share_link + " All " + files.length + " files\n"
bbcode += "\n[url=" + share_link + "]All " + files.length + " files[/url]\n"
markdown += " * [All " + files.length + " files](" + share_link + ")\n"
}
}
const keydown = (e) => {
if (e.ctrlKey || e.altKey || e.metaKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
if (document.activeElement.type && document.activeElement.type === "text") {
return // Prevent shortcuts from interfering with input fields
}
switch (e.key) {
case "u": file_input_field.click(); break
case "t": btn_upload_text.click(); break
case "c": btn_copy_link.copy(); break
case "o": btn_open_link.click(); break
case "q": btn_show_qr.click(); break
case "l": btn_create_list.click(); break
case "e": btn_share_email.click(); break
case "w": btn_share_twitter.click(); break
case "f": btn_share_facebook.click(); break
case "r": btn_share_reddit.click(); break
case "m": btn_share_tumblr.click(); break
case "a": btn_copy_links.copy(); break
case "d": btn_copy_markdown.copy(); break
case "b": btn_copy_bbcode.copy(); break
}
}
</script>
<svelte:window on:paste={paste} on:keydown={keydown} on:beforeunload={leave_confirmation} />
<Konami/>
<!-- If the user is logged in and has used more than 50% of their storage space we will show a progress bar -->
{#if window.user.username !== "" && window.user.storage_space_used/window.user.subscription.storage_space > 0.5}
<section>
<StorageProgressBar used={window.user.storage_space_used} total={window.user.subscription.storage_space}></StorageProgressBar>
</section>
{/if}
<section class="instruction" style="border-top: none;">
<span class="big_number">1</span>
<span class="instruction_text">Select files to upload</span>
<br/>
You can also drop files on this page from your file manager or
paste an image from your clipboard
</section>
<input bind:this={file_input_field} on:change={file_input_change} type="file" name="file" multiple="multiple"/>
<button on:click={() => { file_input_field.click() }} class="big_button button_highlight">
<i class="icon small">cloud_upload</i>
<span><u>U</u>pload Files</span>
</button>
<a bind:this={btn_upload_text} href="/t" id="upload_text_button" class="button big_button button_highlight">
<i class="icon small">text_fields</i>
<span>Upload <u>T</u>ext</span>
</a>
<br/>
<p>
By uploading files to pixeldrain you acknowledge and accept our
<a href="/abuse">content policy</a>.
<p>
<br/>
<section class="instruction" style="margin-bottom: 0;">
<span class="big_number">2</span>
<span class="instruction_text">Wait for the files to finish uploading</span>
<br/>
<UploadStats bind:this={upload_stats} upload_queue={upload_queue}/>
</section>
{#each upload_queue as file}
<UploadProgressBar bind:this={file.component} job={file}></UploadProgressBar>
{/each}
<br/>
<section class="instruction">
<span class="big_number">3</span>
<span class="instruction_text">Share the files</span>
</section>
<br/>
{#if upload_queue.length > 1}
You can create an album to group your files together into one link<br/>
Name:
<form class="album_name_form" on:submit|preventDefault={create_album}>
<input bind:value={input_album_name} type="text" disabled={state !== "finished"} placeholder="My album"/>
<button type="submit" disabled={state !== "finished"}>
<i class="icon">create_new_folder</i> Create
</button>
</form>
<br/><br/>
Other sharing methods:
<br/>
{/if}
<div class="social_buttons" class:hide={!navigator_share}>
<button id="btn_social_share" on:click={share_navigator} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">share</i><br/>
Share
</button>
</div>
<CopyButton bind:this={btn_copy_link} text={share_link} large_icon><u>C</u>opy link</CopyButton>
<button bind:this={btn_open_link} on:click={open_link} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">open_in_new</i>
<span><u>O</u>pen link</span>
</button>
<button bind:this={btn_show_qr} on:click={show_qr_code} class="social_buttons" disabled={state !== "finished"} class:button_highlight={qr_visible}>
<i class="icon">qr_code</i>
<span><u>Q</u>R code</span>
</button>
<div style="display: inline-block;" class:hide={navigator_share}>
<button bind:this={btn_share_email} on:click={share_mail} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">email</i>
<span><u>E</u>-Mail</span>
</button>
<button bind:this={btn_share_twitter} on:click={share_twitter} class="social_buttons" disabled={state !== "finished"}>
<Twitter style="width: 40px; height: 40px;"></Twitter>
<span>T<u>w</u>itter</span>
</button>
<button bind:this={btn_share_facebook} on:click={share_facebook} class="social_buttons" disabled={state !== "finished"}>
<Facebook style="width: 40px; height: 40px;"></Facebook>
<span><u>F</u>acebook</span>
</button>
<button bind:this={btn_share_reddit} on:click={share_reddit} class="social_buttons" disabled={state !== "finished"}>
<Reddit style="width: 40px; height: 40px;"></Reddit>
<span><u>R</u>eddit</span>
</button>
<button bind:this={btn_share_tumblr} on:click={share_tumblr} class="social_buttons" disabled={state !== "finished"}>
<Tumblr style="width: 40px; height: 40px;"></Tumblr>
<span>Tu<u>m</u>blr</span>
</button>
</div>
<br/>
{#if qr_visible}
<img src="/api/misc/qr?text={encodeURIComponent(share_link)}" alt="QR code" style="width: 300px; max-width: 100%;">
<br/>
{/if}
<CopyButton bind:this={btn_copy_links} text={link_list}>Copy <u>a</u>ll links to clipboard</CopyButton>
<CopyButton bind:this={btn_copy_markdown} text={markdown}>Copy mark<u>d</u>own to clipboard</CopyButton>
<CopyButton bind:this={btn_copy_bbcode} text={bbcode}>Copy <u>B</u>BCode to clipboard</CopyButton>
<br/>
{#if window.user.subscription.name === ""}
<section>
<div class="instruction">
<span class="big_number">4</span>
<span class="instruction_text">Support me on Patreon!</span>
</div>
<p>
Pixeldrain costs a lot of money to maintain. Currently the site
makes just barely enough money to pay for hosting. I have never been
able to compensate myself for the hours I have put in developing
this project. Please consider getting a subscription so I can
continue working on pixeldrain and make it even better.
</p>
<p>
Pro costs only <b>€40 per year</b> or <b>€4 per month</b>. You will
get some nice benefits and more features are on the way. You can
help with making pixeldrain the easiest and fastest way to share
files online!
</p>
<br/>
<div style="text-align: center;">
<a href="#pro" class="button big_button" style="min-width: 350px;">
<i class="icon">arrow_downward</i>
Check out Pro
<i class="icon">arrow_downward</i>
</a>
</div>
</section>
{/if}
<br/>
<style>
.big_button {
width: 40%;
min-width: 300px;
max-width: 400px;
margin: 10px;
border-radius: 32px;
font-size: 1.8em;
justify-content: center;
}
.instruction {
border-top: 1px solid var(--separator);
margin: 1em auto;
padding: 5px;
}
.big_number {
font-size: 1.5em;
font-weight: bold;
line-height: 1em;
text-align: center;
display: inline-block;
background: var(--highlight_background);
color: var(--highlight_text_color);
border-radius: 30px;
padding: 0.15em;
margin-right: 0.4em;
width: 1.4em;
height: 1.4em;
vertical-align: middle;
}
.instruction_text {
margin: 0.1em;
font-size: 1.5em;
display: inline;
vertical-align: middle;
}
.album_name_form {
display: inline-flex;
flex-direction: row;
align-items: center;
}
.social_buttons {
flex-direction: column;
min-width: 5em;
}
.social_buttons.hide {
display: none;
}
.social_buttons > .icon {
font-size: 40px;
display: inline-block;
}
.hide {
display: none;
}
</style>