Files
fnx_web/svelte/src/filesystem/upload_widget/FSUploadWidget.svelte

205 lines
4.1 KiB
Svelte
Raw Normal View History

2023-05-27 15:50:44 +02:00
<script>
2024-08-30 17:03:43 +02:00
import { tick } from "svelte";
2023-05-27 15:50:44 +02:00
import { fade } from "svelte/transition";
import UploadProgress from "./UploadProgress.svelte";
export let nav
2023-05-27 15:50:44 +02:00
2025-01-28 12:00:04 +01:00
const max_concurrent_uploads = 5
2024-08-30 17:03:43 +02:00
let file_input_field
let file_input_change = (e) => {
2023-05-27 15:50:44 +02:00
// 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()
}
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
} 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 => {
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
}
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 () => {
// 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)
2025-01-28 12:00:04 +01:00
for (let i = 0; i < upload_queue.length && active_uploads < max_concurrent_uploads; i++) {
2023-05-27 15:50:44 +02:00
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"
nav.reload()
2023-05-27 15:50:44 +02:00
// Empty the queue to free any references to lingering components
2023-05-27 15:50:44 +02:00
upload_queue = []
// 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"
}
}
2024-08-30 17:03:43 +02:00
const finish_upload = () => {
// Update the queue so the status updates are properly rendered
2023-05-27 15:50:44 +02:00
upload_queue = upload_queue
start_upload()
}
2024-08-30 17:03:43 +02:00
const leave_confirmation = (e) => {
2023-05-27 15:50:44 +02:00
if (state === "uploading") {
e.preventDefault()
2024-08-30 17:03:43 +02:00
return "If you close this page your files will stop uploading. Do you want to continue?"
2023-05-27 15:50:44 +02:00
} else {
return null
}
}
</script>
<svelte:window on:beforeunload={leave_confirmation} />
<input
bind:this={file_input_field}
on:change={file_input_change}
2024-08-30 17:03:43 +02:00
class="upload_input" type="file" name="file" multiple
2023-05-27 15:50:44 +02:00
/>
{#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}
<style>
.upload_input {
visibility: hidden;
position: fixed;
width: 0;
height: 0;
}
.upload_widget {
position: fixed;
2023-05-27 15:50:44 +02:00
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>