2023-05-27 15:50:44 +02:00
|
|
|
<script>
|
|
|
|
|
import { createEventDispatcher, tick } from "svelte";
|
|
|
|
|
import { fade } from "svelte/transition";
|
|
|
|
|
import DropUpload from "./DropUpload.svelte";
|
|
|
|
|
import UploadProgress from "./UploadProgress.svelte";
|
|
|
|
|
|
2024-08-09 13:02:07 +02:00
|
|
|
export let nav
|
2023-05-27 15:50:44 +02:00
|
|
|
|
|
|
|
|
let file_input_field;
|
|
|
|
|
let file_input_change = e => {
|
|
|
|
|
// Start uploading the files async
|
|
|
|
|
upload_files(e.target.files)
|
|
|
|
|
|
|
|
|
|
// This resets the file input field
|
|
|
|
|
file_input_field.nodeValue = ""
|
|
|
|
|
}
|
|
|
|
|
export const pick_files = () => {
|
|
|
|
|
file_input_field.click()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export let drop_upload = false
|
|
|
|
|
let visible = false
|
|
|
|
|
let upload_queue = [];
|
|
|
|
|
let task_id_counter = 0
|
|
|
|
|
|
2023-05-29 18:01:23 +02:00
|
|
|
export const upload_files = async files => {
|
2023-05-27 15:50:44 +02:00
|
|
|
if (files.length === 0) {
|
|
|
|
|
return
|
2024-08-09 13:02:07 +02:00
|
|
|
} else if (nav.base.type !== "dir") {
|
2023-05-27 15:50:44 +02:00
|
|
|
alert("Can only upload to directory")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add files to the queue
|
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
|
await upload_file(files[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const upload_file = async file => {
|
2024-08-09 13:02:07 +02:00
|
|
|
if (nav.base.type !== "dir") {
|
2023-05-27 15:50:44 +02:00
|
|
|
alert("Can only upload to directory")
|
|
|
|
|
return
|
2023-05-29 22:15:46 +02:00
|
|
|
} else if (file.type === "" && file.size === 0) {
|
2023-05-27 15:50:44 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-09 13:02:07 +02:00
|
|
|
let path = nav.base.path + "/"
|
2023-05-29 18:01:23 +02:00
|
|
|
if (file.webkitRelativePath) {
|
|
|
|
|
path += file.webkitRelativePath
|
|
|
|
|
} else {
|
|
|
|
|
path += file.name
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-27 15:50:44 +02:00
|
|
|
upload_queue.push({
|
|
|
|
|
task_id: task_id_counter,
|
|
|
|
|
file: file,
|
2023-05-29 18:01:23 +02:00
|
|
|
path: path,
|
2023-05-27 15:50:44 +02:00
|
|
|
component: null,
|
|
|
|
|
status: "queued",
|
|
|
|
|
total_size: file.size,
|
|
|
|
|
loaded_size: 0,
|
|
|
|
|
})
|
|
|
|
|
task_id_counter++
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
if (active_uploads === 0 && state !== "uploading") {
|
|
|
|
|
state = "uploading"
|
|
|
|
|
visible = true
|
|
|
|
|
await tick()
|
|
|
|
|
await start_upload()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let active_uploads = 0
|
|
|
|
|
let state = "idle"
|
|
|
|
|
|
|
|
|
|
const start_upload = async () => {
|
2024-06-13 20:08:10 +02:00
|
|
|
// Count the number of active uploads so we can know how many new uploads we
|
|
|
|
|
// can start
|
|
|
|
|
active_uploads = upload_queue.reduce((acc, val) => {
|
|
|
|
|
if (val.status === "uploading") {
|
|
|
|
|
acc++
|
|
|
|
|
}
|
|
|
|
|
return acc
|
|
|
|
|
}, 0)
|
|
|
|
|
|
2023-05-27 15:50:44 +02:00
|
|
|
for (let i = 0; i < upload_queue.length && active_uploads < 3; i++) {
|
|
|
|
|
if (upload_queue[i]) {
|
|
|
|
|
if (upload_queue[i].status === "queued") {
|
|
|
|
|
active_uploads++
|
|
|
|
|
upload_queue[i].component.start()
|
|
|
|
|
upload_queue[i].status = "uploading"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (active_uploads === 0) {
|
|
|
|
|
state = "finished"
|
2024-08-09 13:02:07 +02:00
|
|
|
nav.reload()
|
2023-05-27 15:50:44 +02:00
|
|
|
|
2024-06-13 20:08:10 +02:00
|
|
|
// Empty the queue to free any references to lingering components
|
2023-05-27 15:50:44 +02:00
|
|
|
upload_queue = []
|
2024-06-13 20:08:10 +02:00
|
|
|
|
|
|
|
|
// In ten seconds we close the popup
|
2023-05-27 15:50:44 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
|
if (state === "finished") {
|
|
|
|
|
visible = false
|
|
|
|
|
}
|
|
|
|
|
}, 10000)
|
|
|
|
|
} else {
|
|
|
|
|
state = "uploading"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const finish_upload = (e) => {
|
2024-06-13 20:08:10 +02:00
|
|
|
// Update the queue so the status updates are properly rendered
|
2023-05-27 15:50:44 +02:00
|
|
|
upload_queue = upload_queue
|
|
|
|
|
start_upload()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const leave_confirmation = e => {
|
|
|
|
|
if (state === "uploading") {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
e.returnValue = "If you close this page your files will stop uploading. Do you want to continue?"
|
|
|
|
|
return e.returnValue
|
|
|
|
|
} else {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:window on:beforeunload={leave_confirmation} />
|
|
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
bind:this={file_input_field}
|
|
|
|
|
on:change={file_input_change}
|
|
|
|
|
class="upload_input" type="file" name="file" multiple="multiple"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{#if visible}
|
|
|
|
|
<div class="upload_widget" transition:fade={{duration: 200}}>
|
|
|
|
|
<div class="header">
|
|
|
|
|
{#if state === "idle"}
|
|
|
|
|
Waiting for files
|
|
|
|
|
{:else if state === "uploading"}
|
|
|
|
|
Uploading files...
|
|
|
|
|
{:else if state === "finished"}
|
|
|
|
|
Done
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="body">
|
|
|
|
|
{#each upload_queue as job}
|
|
|
|
|
{#if job.status !== "finished"}
|
|
|
|
|
<UploadProgress bind:this={job.component} job={job} on:finished={finish_upload}/>
|
|
|
|
|
{/if}
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if drop_upload}
|
|
|
|
|
<DropUpload on:upload_files={e => upload_files(e.detail)} on:upload_file={e => upload_file(e.detail)}/>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.upload_input {
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
position: fixed;
|
|
|
|
|
width: 0;
|
|
|
|
|
height: 0;
|
|
|
|
|
}
|
|
|
|
|
.upload_widget {
|
|
|
|
|
position: absolute;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
width: 500px;
|
|
|
|
|
max-width: 80%;
|
|
|
|
|
height: auto;
|
|
|
|
|
max-height: 50%;
|
|
|
|
|
right: 20px;
|
|
|
|
|
bottom: 20px;
|
2023-11-16 14:48:15 +01:00
|
|
|
border-radius: 8px;
|
2023-05-27 15:50:44 +02:00
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 1px 1px 8px var(--shadow_color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
background: var(--background_color);
|
|
|
|
|
color: var(--background_text_color);
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-size: 1.2em;
|
|
|
|
|
padding: 4px;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
.body {
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
background: var(--body_color);
|
|
|
|
|
color: var(--body_text_color);
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
</style>
|