Move home page into svelte and remove some old junk

This commit is contained in:
2022-02-21 23:25:44 +01:00
parent e4e061869e
commit 692c96eb63
27 changed files with 1497 additions and 2564 deletions

View File

@@ -8,6 +8,7 @@
"name": "svelte-app",
"version": "1.0.0",
"dependencies": {
"behave-js": "^1.5.0",
"chart.js": "^3.7.0"
},
"devDependencies": {
@@ -173,6 +174,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/behave-js": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/behave-js/-/behave-js-1.5.0.tgz",
"integrity": "sha1-RwXsvSxxffD/Kou6MfYeTgY8sGQ="
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -1047,6 +1053,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"behave-js": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/behave-js/-/behave-js-1.5.0.tgz",
"integrity": "sha1-RwXsvSxxffD/Kou6MfYeTgY8sGQ="
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",

View File

@@ -17,6 +17,7 @@
"svelte": "^3.0.0"
},
"dependencies": {
"behave-js": "^1.5.0",
"chart.js": "^3.7.0"
}
}

View File

@@ -38,6 +38,7 @@ export default [
"admin_panel",
"home_page",
"directory_upload",
"text_upload",
].map((name, index) => ({
input: `src/${name}.js`,
output: {

View File

@@ -1,7 +1,7 @@
import App from './home_page/HomePage.svelte';
const app = new App({
target: document.getElementById("uploader"),
target: document.getElementById("page_content"),
props: {}
});

View File

@@ -0,0 +1,296 @@
<script>
import { onMount } from "svelte";
import Modal from "../util/Modal.svelte";
let file_expiry
let direct_linking
onMount(() => {
if (window.location.hash === "#direct_linking" || window.location.hash === "#hotlinking") {
direct_linking.toggle()
}
})
</script>
<div class="feat_table">
<div>
<div></div>
<div class="feat_normal round_tl">Free</div>
<div class="feat_pro feat_highlight round_tr">Pro</div>
</div>
<div>
<div class="feat_label">Size limit per file</div>
<div class="feat_normal">10 GB per file (9.31 GiB)</div>
<div class="feat_pro">
<span class="text_highlight">20 GB</span> per file (18.63 GiB)
</div>
</div>
<div>
<div class="feat_label">
File expiry
</div>
<div class="feat_normal">
30 days after the last time it is viewed
</div>
<div class="feat_pro">
<span class="text_highlight">90 days</span> after the last time it is viewed
<br/>
<button class="round" on:click={file_expiry.toggle}>
<i class="icon">info</i>
More information
</button>
</div>
</div>
<div>
<div class="feat_label">
Data transfer limit
</div>
<div class="feat_normal">
Rate limiting mode will be enabled when a file has 3
times more downloads than views
</div>
<div class="feat_pro">
Transfer limit of <span class="text_highlight">1 terabyte</span>. If
the transfer limit is exceeded advertisements will be enabled again
<br/>
<button class="round" on:click={direct_linking.toggle}>
<i class="icon">info</i>
More information
</button>
</div>
</div>
<div>
<div class="feat_label">
Adver&shy;tise&shy;ments
</div>
<div class="feat_normal">
Banner advertisements on the file viewer page
</div>
<div class="feat_pro">
<span class="text_highlight">No ads</span> on files
you share. No ads when viewing files uploaded by
other users
</div>
</div>
<div>
<div class="feat_label">Privacy</div>
<div class="feat_normal">
No trackers, but advertisers can see your IP address
and browser fingerprint
</div>
<div class="feat_pro">
<span class="text_highlight">Completely
private</span>. No third party scripts and no
logging
</div>
</div>
<div>
<div class="feat_label">
Storage space
</div>
<div class="feat_normal">
500 gigabytes
</div>
<div class="feat_pro">
<span class="text_highlight">1 terabyte</span>
</div>
</div>
<div>
<div class="feat_label">
Download speed
</div>
<div class="feat_normal">
Up to 4 MiB/s, may be slower during busy periods
</div>
<div class="feat_pro">
<span class="text_highlight">High priority</span>
bandwidth for files you download and files on your
account
</div>
</div>
<div>
<div class="feat_label">
Online file previews
</div>
<div class="feat_normal">
View image, audio, PDF and text files directly in your
web browser
</div>
<div class="feat_pro">
<span class="text_highlight">Video streaming</span> in
your web browser. Free users will also be able to watch
videos you uploaded
</div>
</div>
<div>
<div></div>
<div class="feat_normal round_bl">Free</div>
<div class="feat_pro feat_highlight round_br">
{#if window.user.subscription.id === "patreon_1"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
Only
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427" class="button button_highlight round">
€ 2 per month
</a>
or
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12" class="button button_highlight round">
€ 20 per year!
</a>
<br/>
(Excluding tax)
<br/>
Subscription managed by Patreon
{/if}
</div>
</div>
</div>
<Modal bind:this={file_expiry} title="File Expiry Postponing" padding>
<p>
Files on pixeldrain have to expire eventually. If we didn't do
this the website would keep growing forever and we would run out
of money pretty quickly.
</p>
<p>
Unlike most other sharing sites pixeldrain uses a postponing
system for expiring files. When a file is freshly uploaded it
gets 30 days by default (90 days if you have the pro plan).
After these 30 days we will check when the file was last viewed.
Files which are regularly viewed could still bring new users to
the platform, it would be rude to show these people a File Not
Found page. So if the file was viewed in the last 30 days we
will simply postpone the next check a month. If the file was not
viewed however it will immediately be removed.
</p>
<p>
Views are only counted when someone visits the download page in
a web browser. This makes sure that users can see that the file
comes from pixeldrain.
</p>
<p>
This way we can minimize dead links, and you won't have to tell
your friends to 'hurry and download this before it expires'.
</p>
</Modal>
<Modal bind:this={direct_linking} title="Hotlinking Bandwidth" padding>
<p>
Paying for bandwidth is the most expensive part of running
pixeldrain. Because of this we have to limit what can be
downloaded and by who.
</p>
<p>
Normally when you view a file it's on pixeldrain's file viewer.
The file viewer is the page with the download button, the name
of the file and a frame where you can view the file if it's an
image, video, audio, PDF or text file.
</p>
<h3>Rate limiting</h3>
<p>
It's also possible to link directly to a file instead of the
download page. This circumvents our advertisers and branding and
thus we lose money when people do this. That's why I added 'rate
limiting mode' to files. This mode is enabled when a file has
been downloaded three times more than it has been viewed through
the file viewer. When rate limiting mode is activated a file
cannot be downloaded through the API, the request needs to come
from the file viewer page. On the file viewer you will see a
CAPTCHA to fill in when you click the download button.
</p>
<p>
More information about <a
href="https://en.wikipedia.org/wiki/Inline_linking"
target="_blank">Hotlinking on Wikipedia</a>.
</p>
<h3>Hotlinking with a Pro subscription</h3>
<p>
When you have a Pro subscription you will get a monthly data
transfer limit for all the files on your account combined. Files
you download from pixeldrain are subtracted from the data cap.
If you have <a href="/user/subscription">Bandwidth sharing</a>
enabled your data cap is also used when other people download
your files.
</p>
<p>
In principle there is always someone who pays for the bandwidth
usage when a file is being downloaded:
</p>
<ol>
<li>
If the person downloading the file has a Pro subscription
their data cap is used.
</li>
<li>
If the person who uploaded the file has a Pro subscription
and Bandwidth sharing is enabled on their account, then the
uploader's data cap is used.
</li>
<li>
If neither the uploader nor the downloader has a Pro
subscription the download will be supported by
advertisements on the download page.
</li>
</ol>
<p>
The bandwidth cap on your account is a 30 day rolling window.
This means that bandwidth usage will expire 30 days after it was
used. Your counter will not reset at the start of the next
month.
</p>
<p>
When a list of files is downloaded with the 'DL all files'
button each file in the resulting zip file will be counted
separately.
</p>
</Modal>
<style>
.feat_table {
display: flex;
flex-direction: column;
}
.feat_table > div {
display: flex;
flex-direction: row;
}
.feat_table > div > div:first-child {
flex: 0 0 20%;
max-width: 20%;
}
.feat_table > div > div {
flex: 1 1 0;
margin: 0.25em;
padding: 0.5em;
text-align: center;
word-wrap: break-word;
hyphens: auto;
}
.feat_table > div > .feat_label {
border-top-left-radius: 0.5em;
border-bottom-left-radius: 0.5em;
background-color: var(--layer_1_color);
color: var(--layer_1_text_color);
}
.feat_table > div > .feat_normal {
background-color: var(--layer_3_color);
box-shadow: 1px 1px 4px -2px var(--shadow_color);
}
.feat_table > div > .feat_pro {
background-color: var(--layer_4_color);
box-shadow: 1px 1px 4px -1px var(--shadow_color);
}
.feat_table > div > .feat_highlight {
border: 1px solid var(--link_color)
}
.text_highlight {
color: var(--link_color);
font-size: 1.1em;
font-weight: bold;
}
.feat_table > div > div.round_tl { border-top-left-radius: 0.5em; }
.feat_table > div > div.round_tr { border-top-right-radius: 0.5em; }
.feat_table > div > div.round_br { border-bottom-right-radius: 0.5em; }
.feat_table > div > div.round_bl { border-bottom-left-radius: 0.5em; }
</style>

View File

@@ -1,642 +1,101 @@
<script>
import UploadProgressBar from "./UploadProgressBar.svelte"
import { copy_text, 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 { formatDataVolume, formatDuration } from "../util/Formatting.svelte";
import StorageProgressBar from "../user_home/StorageProgressBar.svelte"
import Konami from "../util/Konami.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 = ""
}
let dragging = false
const drop = (e) => {
dragging = false;
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
e.preventDefault()
e.stopPropagation()
upload_files(e.dataTransfer.files)
}
}
const paste = (e) => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
upload_files(e.clipboardData.files)
}
}
let active_uploads = 0
let upload_queue = []
let state = "idle" // idle, uploading, finished
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"
if (stats_interval !== null) {
clearInterval(stats_interval)
stats_interval = null
stats_finished()
}
uploads_finished()
} else {
state = "uploading"
if (stats_interval === null) {
stats_interval = setInterval(stats_update, stats_interval_ms)
}
}
}
const finish_upload = (file) => {
active_uploads--
start_upload()
}
let stats_interval = null
let stats_interval_ms = 500
let progress_bar_inner
let start_time = 0
let total_progress = 0
let total_size = 0
let total_loaded = 0
let last_total_loaded = 0
let total_rate = 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 / total_size
// Calculate ETA by estimating the total time and subtracting the elapsed time
let 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
progress_bar_inner.style.width = (total_progress * 100) + "%"
}
const stats_finished = () => {
start_time = 0
total_loaded = total_size
total_progress = 1
progress_bar_inner.style.width = "100%"
total_rate = 0
}
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 = () => {
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) {
create_list(count+" files", true).then(resp => {
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)
})
}
}
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()
}
const copy_link = () => {
if (copy_text(share_link)) {
console.log('Text copied')
btn_copy_link.querySelector("span").textContent = "Copied!"
btn_copy_link.classList.add("button_highlight")
} else {
console.log('Copying not supported')
btn_copy_link.querySelector("span").textContent = "Failed"
btn_copy_link.classList.add("button_red")
alert("Your browser does not support copying text.")
}
}
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
},
[],
)
}
const copy_links = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: https://pixeldrain.com/u/abcd1234 Some_file.png
text += domain_url() + "/u/" + file.id + " " + file.name + "\n"
})
if (share_link.includes("/l/")) {
text += "\n" + share_link + " All " + files.length + " files\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_links.classList.add("button_highlight")
btn_copy_links.innerHTML = "Links copied to clipboard!"
} else {
btn_copy_links.classList.add("button_red")
btn_copy_links.innerHTML = "Copying links failed"
}
}
const copy_bbcode = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
text += "[url=" + domain_url() + "/u/" + file.id + "]" + file.name + "[/url]\n"
})
if (share_link.includes("/l/")) {
text += "\n[url=" + share_link + "]All " + files.length + " files[/url]\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_bbcode.classList.add("button_highlight")
btn_copy_bbcode.innerHTML = "BBCode copied to clipboard!"
} else {
btn_copy_bbcode.classList.add("button_red")
btn_copy_bbcode.innerHTML = "Copying links failed"
}
}
const copy_markdown = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (files.length > 1) { text += " * " }
text += "[" + file.name + "](" + domain_url() + "/u/" + file.id + ")\n"
})
if (share_link.includes("/l/")) {
text += " * [All " + files.length + " files](" + share_link + ")\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_markdown.classList.add("button_highlight")
btn_copy_markdown.innerHTML = "Markdown copied to clipboard!"
} else {
btn_copy_markdown.classList.add("button_red")
btn_copy_markdown.innerHTML = "Copying links failed"
}
}
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.click(); 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.click(); break
case "d": btn_copy_markdown.click(); break
case "b": btn_copy_bbcode.click(); break
}
}
import FeatureTable from "./FeatureTable.svelte";
import OtherPlans from "./OtherPlans.svelte";
import UploadWidget from "./UploadWidget.svelte";
</script>
<header style="padding-bottom: 80px; padding-top: 80px;">
<picture>
<source media="(max-width: 700px)" srcset="/res/img/header_orbitron.png">
<img class="header_image" src="/res/img/header_orbitron_wide.png" alt="Header">
</picture>
</header>
<svelte:window
on:dragover|preventDefault|stopPropagation={() => { dragging = true }}
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }}
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }}
on:drop={drop}
on:paste={paste}
on:keydown={keydown}
on:beforeunload={leave_confirmation} />
<UploadWidget></UploadWidget>
<Konami></Konami>
<header>
<h1>What is pixeldrain?</h1>
</header>
<div>
<!-- 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="margin-top: 0; 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>
<br/>
<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>
<u>U</u>pload Files
</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>
Upload <u>T</u>ext
</a>
<br/>
<section>
<p>
By uploading files to pixeldrain you acknowledge and accept our
<a href="/about#content-policy">content policy</a>.
Pixeldrain is a file sharing website built for speed and ease of
use. You can upload files you want to share online to our
servers and we will hold on to them for at least a month. During
this time anyone with the link will be able to download your
files. Pixeldrain is built to be as fast as possible, so you
don't have to do any unnecessary waiting when downloading files.
</p>
<p>
<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/>
<div class="stats_box">
<div>Size {formatDataVolume(total_size, 3)}</div>
<div>Progress {(total_progress*100).toPrecision(3)}%</div>
<div>ETA {formatDuration(remaining_time, 0)}</div>
<div>Rate {formatDataVolume(total_rate, 3)}/s</div>
</div>
</section>
<div class="progress_bar_outer">
<div bind:this={progress_bar_inner} class="progress_bar_inner"></div>
</div>
<div id="file_drop_highlight" class="highlight_green" class:hide={!dragging}>
Gimme gimme gimme!<br/>
Drop your files to upload them
Files can be uploaded by clicking the big green upload
button, or by dragging them onto this page from your file
manager.
</p>
<p>
If you uploaded multiple files at once you can also create a
list, which is a collection of files with one single link. Like
a photo album, a music record or a video compilation. Click the
'Create list with uploaded files' button after your uploads are
complete. The files will be saved in the order you uploaded
them.
</p>
</section>
<header>
<h1 id="pro">Getting more out of pixeldrain</h1>
</header>
<section>
<p>
By purchasing a subscription you support pixeldrain on its
mission to make content sharing easier, safer and faster for
everyone. The standard subscription plans use Patreon for
payment processing. Check out our <a href="#prepaid">prepaid
plans</a> if you would like to pay using cryptocurrencies.
</p>
<p>
Pixeldrain uses
<a href="https://en.wikipedia.org/wiki/Byte#Multiple-byte_units"
target="_blank">SI standard units</a> for measuring file sizes.
If you are using Microsoft Windows your files may appear smaller
than they actually are.
</p>
<br/>
<FeatureTable></FeatureTable>
<br/>
<div style="text-align: center;">
Do you need even more time and space? Check out our other plans
</div>
<br/>
<OtherPlans></OtherPlans>
{#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>
<h2 id="prepaid">Prepaid plans</h2>
<p>
You you need more bandwidth or storage space there's also
prepaid plans. For prepaid we charge a base rate of €1 per
month, the rest of the charges are usage based. We charge €4 per
TB per month for storage space and €2 per TB for bandwidth
usage. We accept Bitcoin, Lightning Network and Dogecoin
payments.
</p>
<p>
If €4 per TB of storage is too much we also have plans with
cheaper storage and file expiry enabled. Your files will not
expire as long as they generate traffic, so this can be a viable
option if your files are accessed often.
</p>
<div style="text-align: center;">
<img src="/res/img/coins.png" alt="supported coins" style="width: 250px;"/>
</div>
<button bind:this={btn_copy_link} on:click={copy_link} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">content_copy</i><br/>
<span><u>C</u>opy link</span>
</button>
<button bind:this={btn_open_link} on:click={open_link} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">open_in_new</i><br/>
<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><br/>
<span><u>Q</u>R code</span>
</button>
<div class="social_buttons" 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><br/>
<u>E</u>-Mail
</button>
<button bind:this={btn_share_twitter} on:click={share_twitter} class="social_buttons" disabled={state !== "finished"}>
<Twitter style="width: 40px; height: 40px; margin: 5px 15px;"></Twitter><br/>
T<u>w</u>itter
</button>
<button bind:this={btn_share_facebook} on:click={share_facebook} class="social_buttons" disabled={state !== "finished"}>
<Facebook style="width: 40px; height: 40px; margin: 5px 15px;"></Facebook><br/>
<u>F</u>acebook
</button>
<button bind:this={btn_share_reddit} on:click={share_reddit} class="social_buttons" disabled={state !== "finished"}>
<Reddit style="width: 40px; height: 40px; margin: 5px 15px;"></Reddit><br/>
<u>R</u>eddit
</button>
<button bind:this={btn_share_tumblr} on:click={share_tumblr} class="social_buttons" disabled={state !== "finished"}>
<Tumblr style="width: 40px; height: 40px; margin: 5px 15px;"></Tumblr><br/>
Tu<u>m</u>blr
</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}
<button bind:this={btn_copy_links} on:click={copy_links} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy <u>a</u>ll links to clipboard
</button>
<button bind:this={btn_copy_markdown} on:click={copy_markdown} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy mark<u>d</u>own to clipboard
</button>
<button bind:this={btn_copy_bbcode} on:click={copy_bbcode} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard
</button>
<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 is struggling to get by financially. Because anyone
can upload anything it's hard to find reputable advertisers who
want to advertise on pixeldrain. Every month the ad revenue just
barely covers the bandwidth costs. If this continues I will have
to reduce the file size and bandwidth limits even more. That's
not something I would like to do.
</p>
<p>
Pro costs only <b>€20 per year</b> or €2 per month. 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/>
</div>
<p>
To use prepaid you need to register a pixeldrain account. After
logging in head to the <a href="/user/transactions">transactions
page</a> to deposit your coins.
</p>
</section>
<style>
.big_button{
width: 40%;
min-width: 250px;
max-width: 400px;
margin: 10px !important;
border-radius: 32px;
font-size: 1.8em;
}
.instruction {
border-top: 1px solid var(--layer_2_color_border);
border-bottom: 1px solid var(--layer_2_color_border);
margin: 1.5em 0;
padding: 5px;
}
.big_number {
font-size: 1.5em;
font-weight: bold;
line-height: 1em;
text-align: center;
display: inline-block;
background-color: var(--highlight_color);
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;
}
.stats_box {
display: inline-grid;
grid-template-columns: 25% 25% 25% 25%;
.header_image{
width: 100%;
text-align: center;
font-family: sans-serif, monospace;
}
@media (max-width: 1000px) {
.stats_box {
grid-template-columns: 50% 50%;
}
}
.progress_bar_outer {
width: 100%;
height: 3px;
}
.progress_bar_inner {
background-color: var(--highlight_color);
height: 100%;
width: 0;
transition: width 0.5s;
transition-timing-function: linear;
}
.album_name_form {
display: inline-flex;
flex-direction: row;
align-items: center;
}
.social_buttons {
margin: 5px;
display: inline-block
}
.social_buttons.hide {
display: none;
}
.social_buttons > .icon {
font-size: 40px;
display: inline-block;
width: 40px;
height: 40px;
margin: 5px 15px;
}
.hide {
display: none;
max-width: 800px;
margin: auto;
}
</style>

View File

@@ -0,0 +1,234 @@
<div class="feat_table">
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_5.webp');">
Resolve<br/>
{#if window.user.subscription.id === "patreon_5"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5736701" class="button button_highlight round">
€ 4
</a>
{/if}
</div>
<div class="feat_pro features_cell round_tr">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">180 days</span> file expiry</div>
<div><span class="text_highlight">2 TB</span> transfer limit</div>
<div><span class="text_highlight">2 TB</span> storage space</div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_2.webp');">
Persistence<br/>
{#if window.user.subscription.id === "patreon_2"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291482" class="button button_highlight round">€ 8</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">360 days</span> file expiry</div>
<div><span class="text_highlight">4 TB</span> transfer limit</div>
<div><span class="text_highlight">4 TB</span> storage space</div>
<div>
<span class="text_highlight">File viewer
branding</span>: Set a custom theme and header,
footer and background images on the download pages
for your files
</div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_3.webp');">
Tenacity<br/>
{#if window.user.subscription.id === "patreon_3"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291516" class="button button_highlight round">€ 16</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">8 TB</span> transfer limit</div>
<div><span class="text_highlight">8 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_4.webp');">
Eternity<br/>
{#if window.user.subscription.id === "patreon_4"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291528" class="button button_highlight round">€ 32</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">16 TB</span> transfer limit</div>
<div><span class="text_highlight">16 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_6.webp');">
Infinity<br/>
{#if window.user.subscription.id === "patreon_6"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=6573749" class="button button_highlight round">€ 64</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">32 TB</span> transfer limit</div>
<div><span class="text_highlight">32 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_7.webp');">
Omnipotence<br/>
{#if window.user.subscription.id === "patreon_7"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=7732256" class="button button_highlight round">
€ 96
</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">48 TB</span> transfer limit</div>
<div><span class="text_highlight">48 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_8.webp');">
Omnipresence<br/>
{#if window.user.subscription.id === "patreon_8"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=7732262" class="button button_highlight round">
€ 128
</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">64 TB</span> transfer limit</div>
<div><span class="text_highlight">64 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_9.webp');">
Omniscience<br/>
{#if window.user.subscription.id === "patreon_9"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=7732266" class="button button_highlight round">
€ 192
</a>
{/if}
</div>
<div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">96 TB</span> transfer limit</div>
<div><span class="text_highlight">96 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
<div>
<div class="cell_background" style="background-image: url('/res/img/benefit_10.webp');">
Trinity<br/>
{#if window.user.subscription.id === "patreon_10"}
You have this plan<br/>
Thank you for supporting pixeldrain!
{:else}
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=7732271" class="button button_highlight round">
€ 256
</a>
{/if}
</div>
<div class="feat_pro features_cell round_br">
<div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">128 TB</span> transfer limit</div>
<div><span class="text_highlight">128 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div>
</div>
</div>
<style>
.feat_table {
display: flex;
flex-direction: column;
}
.feat_table > div {
display: flex;
flex-direction: row;
}
.feat_table > div > div:first-child {
flex: 0 0 20%;
max-width: 20%;
}
.feat_table > div > div {
flex: 1 1 0;
margin: 0.25em;
padding: 0.5em;
text-align: center;
word-wrap: break-word;
hyphens: auto;
}
.feat_table > div > .feat_pro {
background-color: var(--layer_4_color);
box-shadow: 1px 1px 4px -1px var(--shadow_color);
}
.text_highlight {
color: var(--link_color);
font-size: 1.1em;
font-weight: bold;
}
.feat_table > div > .cell_background {
flex: 0 0 33%;
min-width: 33%;
border-top-left-radius: 0.5em;
border-bottom-left-radius: 0.5em;
background-position: center;
background-size: cover;
text-align: left;
font-size: 1.2em;
color: #ffffff;
text-shadow: 1px 1px 3px #000000;
}
.feat_table > div > div.round_tr { border-top-right-radius: 0.5em; }
.feat_table > div > div.round_br { border-bottom-right-radius: 0.5em; }
.features_cell {
display: flex;
flex-wrap: wrap;
}
.features_cell > div {
flex: 1 1 50%;
min-width: 220px;
}
</style>

View File

@@ -1,5 +1,5 @@
<script>
import { domain_url } from "../util/Util.svelte"
import { add_upload_history, domain_url } from "../util/Util.svelte"
import { formatDataVolume, formatDuration} from "../util/Formatting.svelte"
export let job = {}
@@ -160,29 +160,6 @@ export const start = () => {
xhr.send(job.file);
}
const add_upload_history = id => {
// Make sure the user is not logged in, for privacy. This keeps the
// files uploaded while logged in and anonymously uploaded files
// separated
if (document.cookie.includes("pd_auth_key")) { return; }
let uploads = localStorage.getItem("uploaded_files");
if (uploads === null) { uploads = ""; }
// Check if there are not too many values stored
if (uploads.length > 3600) {
// 3600 characters is enough to store 400 file IDs. If we exceed that
// number we'll drop the last two items
uploads = uploads.substring(
uploads.indexOf(",") + 1
).substring(
uploads.indexOf(",") + 1
);
}
// Save the new ID
localStorage.setItem("uploaded_files", id + "," + uploads);
}
</script>
<a bind:this={file_button} class="upload_task" {href} {target}>

View File

@@ -0,0 +1,642 @@
<script>
import UploadProgressBar from "./UploadProgressBar.svelte"
import { copy_text, 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 { formatDataVolume, formatDuration } from "../util/Formatting.svelte";
import StorageProgressBar from "../user_home/StorageProgressBar.svelte"
import Konami from "../util/Konami.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 = ""
}
let dragging = false
const drop = (e) => {
dragging = false;
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
e.preventDefault()
e.stopPropagation()
upload_files(e.dataTransfer.files)
}
}
const paste = (e) => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
upload_files(e.clipboardData.files)
}
}
let active_uploads = 0
let upload_queue = []
let state = "idle" // idle, uploading, finished
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"
if (stats_interval !== null) {
clearInterval(stats_interval)
stats_interval = null
stats_finished()
}
uploads_finished()
} else {
state = "uploading"
if (stats_interval === null) {
stats_interval = setInterval(stats_update, stats_interval_ms)
}
}
}
const finish_upload = (file) => {
active_uploads--
start_upload()
}
let stats_interval = null
let stats_interval_ms = 500
let progress_bar_inner
let start_time = 0
let total_progress = 0
let total_size = 0
let total_loaded = 0
let last_total_loaded = 0
let total_rate = 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 / total_size
// Calculate ETA by estimating the total time and subtracting the elapsed time
let 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
progress_bar_inner.style.width = (total_progress * 100) + "%"
}
const stats_finished = () => {
start_time = 0
total_loaded = total_size
total_progress = 1
progress_bar_inner.style.width = "100%"
total_rate = 0
}
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 = () => {
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) {
create_list(count+" files", true).then(resp => {
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)
})
}
}
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()
}
const copy_link = () => {
if (copy_text(share_link)) {
console.log('Text copied')
btn_copy_link.querySelector("span").textContent = "Copied!"
btn_copy_link.classList.add("button_highlight")
} else {
console.log('Copying not supported')
btn_copy_link.querySelector("span").textContent = "Failed"
btn_copy_link.classList.add("button_red")
alert("Your browser does not support copying text.")
}
}
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
},
[],
)
}
const copy_links = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: https://pixeldrain.com/u/abcd1234 Some_file.png
text += domain_url() + "/u/" + file.id + " " + file.name + "\n"
})
if (share_link.includes("/l/")) {
text += "\n" + share_link + " All " + files.length + " files\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_links.classList.add("button_highlight")
btn_copy_links.innerHTML = "Links copied to clipboard!"
} else {
btn_copy_links.classList.add("button_red")
btn_copy_links.innerHTML = "Copying links failed"
}
}
const copy_bbcode = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
text += "[url=" + domain_url() + "/u/" + file.id + "]" + file.name + "[/url]\n"
})
if (share_link.includes("/l/")) {
text += "\n[url=" + share_link + "]All " + files.length + " files[/url]\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_bbcode.classList.add("button_highlight")
btn_copy_bbcode.innerHTML = "BBCode copied to clipboard!"
} else {
btn_copy_bbcode.classList.add("button_red")
btn_copy_bbcode.innerHTML = "Copying links failed"
}
}
const copy_markdown = () => {
// Add the text to the textarea
let text = ""
let files = get_finished_files()
files.forEach(file => {
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (files.length > 1) { text += " * " }
text += "[" + file.name + "](" + domain_url() + "/u/" + file.id + ")\n"
})
if (share_link.includes("/l/")) {
text += " * [All " + files.length + " files](" + share_link + ")\n"
}
// Copy the selected text
if (copy_text(text)) {
btn_copy_markdown.classList.add("button_highlight")
btn_copy_markdown.innerHTML = "Markdown copied to clipboard!"
} else {
btn_copy_markdown.classList.add("button_red")
btn_copy_markdown.innerHTML = "Copying links failed"
}
}
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.click(); 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.click(); break
case "d": btn_copy_markdown.click(); break
case "b": btn_copy_bbcode.click(); break
}
}
</script>
<svelte:window
on:dragover|preventDefault|stopPropagation={() => { dragging = true }}
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }}
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }}
on:drop={drop}
on:paste={paste}
on:keydown={keydown}
on:beforeunload={leave_confirmation} />
<Konami></Konami>
<div>
<!-- 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="margin-top: 0; 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>
<br/>
<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>
<u>U</u>pload Files
</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>
Upload <u>T</u>ext
</a>
<br/>
<p>
By uploading files to pixeldrain you acknowledge and accept our
<a href="/about#content-policy">content policy</a>.
<p>
<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/>
<div class="stats_box">
<div>Size {formatDataVolume(total_size, 3)}</div>
<div>Progress {(total_progress*100).toPrecision(3)}%</div>
<div>ETA {formatDuration(remaining_time, 0)}</div>
<div>Rate {formatDataVolume(total_rate, 3)}/s</div>
</div>
</section>
<div class="progress_bar_outer">
<div bind:this={progress_bar_inner} class="progress_bar_inner"></div>
</div>
<div id="file_drop_highlight" class="highlight_green" class:hide={!dragging}>
Gimme gimme gimme!<br/>
Drop your files to upload them
</div>
<br/>
{#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>
<button bind:this={btn_copy_link} on:click={copy_link} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">content_copy</i><br/>
<span><u>C</u>opy link</span>
</button>
<button bind:this={btn_open_link} on:click={open_link} class="social_buttons" disabled={state !== "finished"}>
<i class="icon">open_in_new</i><br/>
<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><br/>
<span><u>Q</u>R code</span>
</button>
<div class="social_buttons" 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><br/>
<u>E</u>-Mail
</button>
<button bind:this={btn_share_twitter} on:click={share_twitter} class="social_buttons" disabled={state !== "finished"}>
<Twitter style="width: 40px; height: 40px; margin: 5px 15px;"></Twitter><br/>
T<u>w</u>itter
</button>
<button bind:this={btn_share_facebook} on:click={share_facebook} class="social_buttons" disabled={state !== "finished"}>
<Facebook style="width: 40px; height: 40px; margin: 5px 15px;"></Facebook><br/>
<u>F</u>acebook
</button>
<button bind:this={btn_share_reddit} on:click={share_reddit} class="social_buttons" disabled={state !== "finished"}>
<Reddit style="width: 40px; height: 40px; margin: 5px 15px;"></Reddit><br/>
<u>R</u>eddit
</button>
<button bind:this={btn_share_tumblr} on:click={share_tumblr} class="social_buttons" disabled={state !== "finished"}>
<Tumblr style="width: 40px; height: 40px; margin: 5px 15px;"></Tumblr><br/>
Tu<u>m</u>blr
</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}
<button bind:this={btn_copy_links} on:click={copy_links} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy <u>a</u>ll links to clipboard
</button>
<button bind:this={btn_copy_markdown} on:click={copy_markdown} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy mark<u>d</u>own to clipboard
</button>
<button bind:this={btn_copy_bbcode} on:click={copy_bbcode} disabled={state !== "finished"}>
<i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard
</button>
<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 is struggling to get by financially. Because anyone
can upload anything it's hard to find reputable advertisers who
want to advertise on pixeldrain. Every month the ad revenue just
barely covers the bandwidth costs. If this continues I will have
to reduce the file size and bandwidth limits even more. That's
not something I would like to do.
</p>
<p>
Pro costs only <b>€20 per year</b> or €2 per month. 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/>
</div>
<style>
.big_button{
width: 40%;
min-width: 250px;
max-width: 400px;
margin: 10px !important;
border-radius: 32px;
font-size: 1.8em;
}
.instruction {
border-top: 1px solid var(--layer_2_color_border);
border-bottom: 1px solid var(--layer_2_color_border);
margin: 1.5em 0;
padding: 5px;
}
.big_number {
font-size: 1.5em;
font-weight: bold;
line-height: 1em;
text-align: center;
display: inline-block;
background-color: var(--highlight_color);
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;
}
.stats_box {
display: inline-grid;
grid-template-columns: 25% 25% 25% 25%;
width: 100%;
text-align: center;
font-family: sans-serif, monospace;
}
@media (max-width: 1000px) {
.stats_box {
grid-template-columns: 50% 50%;
}
}
.progress_bar_outer {
width: 100%;
height: 3px;
}
.progress_bar_inner {
background-color: var(--highlight_color);
height: 100%;
width: 0;
transition: width 0.5s;
transition-timing-function: linear;
}
.album_name_form {
display: inline-flex;
flex-direction: row;
align-items: center;
}
.social_buttons {
margin: 5px;
display: inline-block
}
.social_buttons.hide {
display: none;
}
.social_buttons > .icon {
font-size: 40px;
display: inline-block;
width: 40px;
height: 40px;
margin: 5px 15px;
}
.hide {
display: none;
}
</style>

View File

@@ -0,0 +1,8 @@
import App from './text_upload/TextUpload.svelte';
const app = new App({
target: document.getElementById("body"),
props: {}
});
export default app;

View File

@@ -0,0 +1,145 @@
<script>
import { onMount } from "svelte";
import Modal from "../util/Modal.svelte";
import Behave from "behave-js";
import { add_upload_history } from "../util/Util.svelte";
let textarea
let help
onMount(() => {
new Behave({
textarea: textarea,
autoStrip: false,
autoOpen: false,
overwrite: false,
autoIndent: false,
replaceTab: true,
softTabs: false,
tabSize: 8
});
})
const upload_text = async () => {
var filename = prompt(
"What do you want to call this piece of textual art?\n\n" +
"Please add your own file extension, if you want.",
"Text file.txt"
);
if (!filename){
return; // User pressed cancel
}
try {
let form = new FormData()
form.append("name", filename)
form.append("file", new Blob([textarea.value], {type: "text/plain"}))
let resp = await fetch(
window.api_endpoint+"/file",
{method: "POST", body: form}
)
if(resp.status >= 400) {
throw new Error(await resp.text());
}
let id = (await resp.json()).id
add_upload_history(id)
window.location.href = "/u/" + id
} catch (err) {
alert("File upload failed: " + err)
return
}
}
// Upload the file when ctrl + s is pressed
const keydown = e => {
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
e.preventDefault()
upload_text();
return false;
}
}
</script>
<svelte:window on:keydown={keydown}></svelte:window>
<div id="text_editor" class="text_editor">
<div id="headerbar" class="highlight_2 headerbar">
<a href="/" class="button round">
<i class="icon">arrow_back</i>
</a>
<div id="headerbar_spacer" class="headerbar_spacer"></div>
<button class="button toolbar_button round" on:click={help.toggle}>
<i class="icon">info</i> Information
</button>
<button class="button toolbar_button round button_highlight" on:click={upload_text}>
<i class="icon">save</i> Save
</button>
</div>
<div class="textarea_container">
<!-- svelte-ignore a11y-autofocus -->
<textarea bind:this={textarea} class="textarea" placeholder="Your text here..." autofocus="autofocus"></textarea>
</div>
</div>
<Modal bind:this={help} title="Text editor help" padding width="500px">
<p>
You can type anything you want in here. When you're done press
CTRL + S or click the Save button in the top right corner to
upload your text file to pixeldrain.
</p>
<p>
To show syntax highlighting on pixeldrain's file viewer you
should save your file with a file extension like .js, .go,
.java, etc. If you save your file with the extension .md or
.markdown the result will be rendered as HTML on the file
viewer.
</p>
<p>
The text editor has been enhanced by Jacob Kelley's
<a href="https://jakiestfu.github.io/Behave.js/" target="_blank">Behave.js</a>.
Many thanks to him for developing this plugin and putting it
under the MIT license.
</p>
</Modal>
<style>
.text_editor {
position: absolute;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.headerbar {
flex: 0 0 auto;
display: flex;
flex-direction: row;
}
.headerbar > * {
flex: 0 0 auto;
margin-left: 6px;
margin-right: 6px;
}
.headerbar > .headerbar_spacer { flex: 1 1 auto; }
.textarea_container {
flex: 1 1 auto;
margin: 0;
z-index: 9;
}
.textarea {
position: relative;
height: 100%;
width: 100%;
background: var(--layer_1_color);
color: var(--text_color);
margin: 0;
border-radius: 0;
box-shadow: none;
}
.textarea:focus { box-shadow: none; }
</style>

View File

@@ -10,6 +10,7 @@ import { fade } from 'svelte/transition';
export let title = "";
export let width = "800px";
export let height = "auto";
export let padding = false;
let visible = false;
const load_bg = background => {
@@ -56,7 +57,7 @@ const keydown = e => {
</button>
</slot>
</div>
<div class="body">
<div class="body" class:padding>
<slot></slot>
</div>
</div>
@@ -120,4 +121,7 @@ const keydown = e => {
flex-shrink: 1;
overflow: auto;
}
.padding {
padding: 10px;
}
</style>

View File

@@ -35,4 +35,28 @@ export function domain_url() {
return url;
}
export const add_upload_history = id => {
// Make sure the user is not logged in, for privacy. This keeps the
// files uploaded while logged in and anonymously uploaded files
// separated
if (document.cookie.includes("pd_auth_key")) { return; }
let uploads = localStorage.getItem("uploaded_files");
if (uploads === null) { uploads = ""; }
// Check if there are not too many values stored
if (uploads.length > 3600) {
// 3600 characters is enough to store 400 file IDs. If we exceed that
// number we'll drop the last two items
uploads = uploads.substring(
uploads.indexOf(",") + 1
).substring(
uploads.indexOf(",") + 1
);
}
// Save the new ID
localStorage.setItem("uploaded_files", id + "," + uploads);
}
</script>