2021-06-22 17:14:21 +02:00
|
|
|
<script>
|
|
|
|
import { domain_url } from "../util/Util.svelte"
|
|
|
|
import { formatDataVolume, formatDuration} from "../util/Formatting.svelte"
|
|
|
|
|
|
|
|
export let job = {}
|
|
|
|
let file_button
|
2021-06-29 12:07:49 +02:00
|
|
|
let progress_bar
|
2021-06-22 17:14:21 +02:00
|
|
|
let tries = 0
|
|
|
|
let start_time = 0
|
|
|
|
let remaining_time = 0
|
|
|
|
|
2021-06-29 12:07:49 +02:00
|
|
|
let stats_interval = null
|
2021-06-29 13:08:57 +02:00
|
|
|
let stats_interval_ms = 250
|
2021-06-28 12:01:18 +02:00
|
|
|
let progress = 0
|
2021-06-22 17:14:21 +02:00
|
|
|
let last_loaded_size = 0
|
2021-06-29 12:07:49 +02:00
|
|
|
let transfer_rate = 0
|
2021-06-22 17:14:21 +02:00
|
|
|
const on_progress = () => {
|
2021-06-28 12:01:18 +02:00
|
|
|
if (job.loaded_size === 0 || job.total_size === 0) {
|
|
|
|
return
|
|
|
|
}
|
2021-06-22 17:14:21 +02:00
|
|
|
|
2021-06-28 12:01:18 +02:00
|
|
|
progress = job.loaded_size / job.total_size
|
2021-06-29 12:07:49 +02:00
|
|
|
let elapsed_time = new Date().getTime() - start_time
|
2021-06-28 12:01:18 +02:00
|
|
|
remaining_time = (elapsed_time/progress) - elapsed_time
|
2021-06-22 17:14:21 +02:00
|
|
|
|
2021-06-29 12:07:49 +02:00
|
|
|
// Calculate transfer rate, apply smoothing by mixing it with the previous
|
|
|
|
// rate ten to one
|
|
|
|
transfer_rate = Math.floor(
|
|
|
|
(transfer_rate * 0.9) +
|
|
|
|
(((1000 / stats_interval_ms) * (job.loaded_size - last_loaded_size)) * 0.1)
|
|
|
|
)
|
2021-06-22 17:14:21 +02:00
|
|
|
|
2021-06-28 12:01:18 +02:00
|
|
|
last_loaded_size = job.loaded_size
|
2021-06-22 17:14:21 +02:00
|
|
|
|
2021-06-29 12:07:49 +02:00
|
|
|
progress_bar.style.width = (progress * 100) + "%"
|
2021-06-22 17:14:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let href = null
|
|
|
|
let target = null
|
|
|
|
const on_success = (resp) => {
|
2021-06-29 12:07:49 +02:00
|
|
|
clearInterval(stats_interval)
|
|
|
|
stats_interval = null
|
|
|
|
transfer_rate = 0
|
|
|
|
job.loaded_size = job.total_size
|
|
|
|
job.file = null // Delete reference to file to free memory
|
2021-06-28 12:01:18 +02:00
|
|
|
|
2021-06-22 17:14:21 +02:00
|
|
|
job.id = resp.id
|
|
|
|
job.status = "finished"
|
|
|
|
job.on_finished(job)
|
|
|
|
|
|
|
|
add_upload_history(resp.id)
|
|
|
|
|
|
|
|
href = "/u/"+resp.id
|
|
|
|
target = "_blank"
|
|
|
|
|
|
|
|
file_button.style.background = 'var(--layer_3_color)'
|
2021-06-29 12:07:49 +02:00
|
|
|
progress_bar.style.width = "100%"
|
2021-06-29 13:08:57 +02:00
|
|
|
progress_bar.style.opacity = "0"
|
2021-06-22 17:14:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let error_id = ""
|
|
|
|
let error_reason = ""
|
|
|
|
const on_failure = (status, message) => {
|
2021-06-29 12:07:49 +02:00
|
|
|
clearInterval(stats_interval)
|
|
|
|
stats_interval = null
|
|
|
|
transfer_rate = 0
|
2021-06-28 12:01:18 +02:00
|
|
|
job.loaded_size = job.total_size
|
2021-06-29 12:07:49 +02:00
|
|
|
job.file = null // Delete reference to file to free memory
|
2021-06-28 12:01:18 +02:00
|
|
|
|
2021-06-22 17:14:21 +02:00
|
|
|
error_id = status
|
|
|
|
error_reason = message
|
|
|
|
job.status = "error"
|
|
|
|
file_button.style.background = 'var(--danger_color)'
|
|
|
|
file_button.style.color = 'var(--highlight_text_color)'
|
2021-06-29 12:07:49 +02:00
|
|
|
progress_bar.style.width = "0"
|
2021-06-22 17:14:21 +02:00
|
|
|
|
|
|
|
job.on_finished(job)
|
|
|
|
}
|
|
|
|
|
|
|
|
export const start = () => {
|
|
|
|
start_time = new Date().getTime()
|
2021-06-29 12:07:49 +02:00
|
|
|
stats_interval = setInterval(on_progress, stats_interval_ms)
|
2021-06-22 17:14:21 +02:00
|
|
|
|
|
|
|
let form = new FormData();
|
|
|
|
form.append('file', job.file, job.name);
|
|
|
|
|
|
|
|
let xhr = new XMLHttpRequest();
|
|
|
|
xhr.open("POST", window.api_endpoint+"/file", true);
|
|
|
|
xhr.timeout = 21600000; // 6 hours, to account for slow connections
|
|
|
|
|
|
|
|
xhr.upload.addEventListener("progress", evt => {
|
|
|
|
if (evt.lengthComputable) {
|
2021-06-28 12:01:18 +02:00
|
|
|
job.loaded_size = evt.loaded
|
|
|
|
job.total_size = evt.total
|
2021-06-22 17:14:21 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
xhr.onreadystatechange = () => {
|
|
|
|
// readystate 4 means the upload is done
|
|
|
|
if (xhr.readyState !== 4) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xhr.status >= 100 && xhr.status < 400) {
|
|
|
|
// Request is a success
|
|
|
|
on_success(JSON.parse(xhr.response))
|
|
|
|
} else if (xhr.status >= 400) {
|
|
|
|
// Request failed
|
|
|
|
console.log("Upload error. status: " + xhr.status + " response: " + xhr.response);
|
2021-07-06 20:08:51 +02:00
|
|
|
|
|
|
|
let resp = {}
|
|
|
|
if (xhr.status === 429) {
|
|
|
|
resp = {
|
|
|
|
value: "too_many_requests",
|
|
|
|
message: "Too many requests. Please wait a few seconds",
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resp = JSON.parse(xhr.response)
|
|
|
|
}
|
2021-06-22 17:14:21 +02:00
|
|
|
|
|
|
|
if (resp.value == "file_too_large" || resp.value == "ip_banned" || tries === 3) {
|
|
|
|
// Permanent failure
|
|
|
|
on_failure(resp.value, resp.message)
|
|
|
|
} else {
|
|
|
|
// Temporary failure, try again in 5 seconds
|
|
|
|
tries++
|
|
|
|
setTimeout(start, 5000)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Request did not arrive
|
|
|
|
if (tries < 3) {
|
|
|
|
// Try again
|
|
|
|
tries++
|
|
|
|
setTimeout(start, 5000)
|
|
|
|
} else {
|
|
|
|
// Give up after three tries
|
|
|
|
on_failure(xhr.responseText, xhr.responseText)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
xhr.send(form);
|
|
|
|
}
|
|
|
|
|
|
|
|
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}>
|
2021-06-29 12:07:49 +02:00
|
|
|
<div class="top_half">
|
|
|
|
<div class="thumbnail">
|
2021-06-22 17:14:21 +02:00
|
|
|
{#if job.status === "queued"}
|
2021-06-29 12:07:49 +02:00
|
|
|
<i class="icon">cloud_queue</i>
|
2021-06-22 17:14:21 +02:00
|
|
|
{:else if job.status === "uploading"}
|
2021-06-29 12:07:49 +02:00
|
|
|
<i class="icon">cloud_upload</i>
|
2021-06-22 17:14:21 +02:00
|
|
|
{:else if job.status === "finished"}
|
2021-06-29 12:07:49 +02:00
|
|
|
<img src="/api/file/{job.id}/thumbnail" alt="file thumbnail" />
|
2021-06-22 17:14:21 +02:00
|
|
|
{:else if job.status === "error"}
|
2021-06-29 12:07:49 +02:00
|
|
|
<i class="icon">error</i>
|
2021-06-22 17:14:21 +02:00
|
|
|
{/if}
|
|
|
|
</div>
|
2021-06-29 12:07:49 +02:00
|
|
|
<div class="queue_body">
|
|
|
|
<div class="title">
|
|
|
|
{#if job.status === "error"}
|
|
|
|
{error_reason}
|
|
|
|
{:else}
|
|
|
|
{job.name}
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
<div class="stats">
|
|
|
|
{#if job.status === "queued"}
|
|
|
|
Queued...
|
|
|
|
{:else if job.status === "uploading"}
|
|
|
|
<div class="stat">
|
|
|
|
{(progress*100).toPrecision(3)}%
|
|
|
|
</div>
|
|
|
|
<div class="stat">
|
|
|
|
ETA {formatDuration(remaining_time, 0)}
|
|
|
|
</div>
|
|
|
|
<div class="stat">
|
|
|
|
{formatDataVolume(transfer_rate, 3)}/s
|
|
|
|
</div>
|
|
|
|
{:else if job.status === "finished"}
|
|
|
|
<span class="file_link">
|
|
|
|
{domain_url() + "/u/" + job.id}
|
|
|
|
</span>
|
|
|
|
{:else if job.status === "error"}
|
|
|
|
{error_id}
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="progress">
|
|
|
|
<div bind:this={progress_bar} class="progress_bar"></div>
|
2021-06-22 17:14:21 +02:00
|
|
|
</div>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
|
|
.upload_task{
|
|
|
|
position: relative;
|
|
|
|
box-sizing: border-box;
|
2021-06-29 12:07:49 +02:00
|
|
|
width: 440px;
|
|
|
|
max-width: 95%;
|
|
|
|
height: 4em;
|
2021-06-22 17:14:21 +02:00
|
|
|
margin: 10px;
|
|
|
|
padding: 0;
|
|
|
|
overflow: hidden;
|
2021-08-17 18:24:21 +02:00
|
|
|
border-radius: 6px;
|
2021-06-22 17:14:21 +02:00
|
|
|
box-shadow: 2px 2px 8px -3px var(--shadow_color);
|
|
|
|
background-color: var(--layer_3_color);
|
|
|
|
color: var(--text_color);
|
|
|
|
word-break: break-all;
|
|
|
|
text-align: left;
|
|
|
|
line-height: 1.2em;
|
|
|
|
display: inline-flex;
|
2021-06-29 12:07:49 +02:00
|
|
|
flex-direction: column;
|
2021-06-22 17:14:21 +02:00
|
|
|
transition: box-shadow 0.3s, opacity 2s;
|
|
|
|
white-space: normal;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
text-decoration: none;
|
|
|
|
vertical-align: top;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.top_half {
|
|
|
|
flex: 1 1 auto;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
2021-06-22 17:14:21 +02:00
|
|
|
.upload_task:hover {
|
|
|
|
box-shadow: 0 0 2px 2px var(--highlight_color), inset 0 0 1px 1px var(--highlight_color);
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
|
2021-06-29 12:07:49 +02:00
|
|
|
.thumbnail {
|
2021-06-22 17:14:21 +02:00
|
|
|
display: flex;
|
|
|
|
flex: 0 0 auto;
|
2021-06-29 13:08:57 +02:00
|
|
|
width: 4em;
|
2021-06-22 17:14:21 +02:00
|
|
|
margin-right: 4px;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.thumbnail > img {
|
2021-06-29 13:08:57 +02:00
|
|
|
width: 90%;
|
|
|
|
border-radius: 4px;
|
2021-06-22 17:14:21 +02:00
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.thumbnail > i {
|
2021-06-22 17:14:21 +02:00
|
|
|
font-size: 3em;
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.queue_body {
|
2021-06-22 17:14:21 +02:00
|
|
|
flex: 1 1 auto;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.queue_body > .title {
|
2021-06-22 17:14:21 +02:00
|
|
|
flex: 1 1 auto;
|
2021-06-28 12:01:18 +02:00
|
|
|
overflow: hidden;
|
2021-06-22 17:14:21 +02:00
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.queue_body > .stats {
|
2021-06-22 17:14:21 +02:00
|
|
|
flex: 0 0 auto;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
height: 1.4em;
|
|
|
|
border-top: 1px solid var(--layer_3_color_border);
|
|
|
|
text-align: center;
|
2021-06-28 12:01:18 +02:00
|
|
|
font-family: sans-serif, monospace;
|
2021-06-22 17:14:21 +02:00
|
|
|
font-size: 0.9em;
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.queue_body > .stats > .stat {
|
2021-06-22 17:14:21 +02:00
|
|
|
flex: 0 1 100%;
|
|
|
|
}
|
|
|
|
.file_link{
|
|
|
|
color: var(--highlight_color);
|
|
|
|
}
|
2021-06-29 12:07:49 +02:00
|
|
|
.progress {
|
|
|
|
flex: 0 0 auto;
|
|
|
|
height: 2px;
|
|
|
|
}
|
|
|
|
.progress_bar {
|
|
|
|
background-color: var(--highlight_color);
|
|
|
|
height: 100%;
|
|
|
|
width: 0;
|
2021-06-29 13:08:57 +02:00
|
|
|
transition: width 0.25s, opacity 3s;
|
|
|
|
transition-timing-function: linear, ease;
|
2021-06-29 12:07:49 +02:00
|
|
|
}
|
2021-06-22 17:14:21 +02:00
|
|
|
</style>
|