Move home page into svelte and remove some old junk
This commit is contained in:
11
svelte/package-lock.json
generated
11
svelte/package-lock.json
generated
@@ -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",
|
||||
|
@@ -17,6 +17,7 @@
|
||||
"svelte": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"behave-js": "^1.5.0",
|
||||
"chart.js": "^3.7.0"
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ export default [
|
||||
"admin_panel",
|
||||
"home_page",
|
||||
"directory_upload",
|
||||
"text_upload",
|
||||
].map((name, index) => ({
|
||||
input: `src/${name}.js`,
|
||||
output: {
|
||||
|
@@ -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: {}
|
||||
});
|
||||
|
||||
|
296
svelte/src/home_page/FeatureTable.svelte
Normal file
296
svelte/src/home_page/FeatureTable.svelte
Normal 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­tise­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>
|
@@ -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>
|
||||
|
234
svelte/src/home_page/OtherPlans.svelte
Normal file
234
svelte/src/home_page/OtherPlans.svelte
Normal 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>
|
@@ -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}>
|
||||
|
642
svelte/src/home_page/UploadWidget.svelte
Normal file
642
svelte/src/home_page/UploadWidget.svelte
Normal 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>
|
8
svelte/src/text_upload.js
Normal file
8
svelte/src/text_upload.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import App from './text_upload/TextUpload.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
145
svelte/src/text_upload/TextUpload.svelte
Normal file
145
svelte/src/text_upload/TextUpload.svelte
Normal 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>
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user