Allow drag-and-drop uploading in album viewer

This commit is contained in:
2023-03-21 23:37:46 +01:00
parent b2101b3243
commit 236e282777
7 changed files with 195 additions and 96 deletions

View File

@@ -21,6 +21,7 @@ import CustomBanner from "./CustomBanner.svelte";
import LoadingIndicator from "../util/LoadingIndicator.svelte";
import TransferLimit from "./TransferLimit.svelte";
import ListStats from "./ListStats.svelte";
import ListUpdater from "./ListUpdater.svelte";
let loading = true
let embedded = false
@@ -73,6 +74,7 @@ let toolbar_toggle = () => {
}
let downloader
let list_updater
let details_window
let details_visible = false
let qr_window
@@ -354,6 +356,10 @@ const keyboard_event = evt => {
case "q": // Q to close the window
window.close()
break
case "u": // U to upload new files
if (list_updater) {
list_updater.pick_files()
}
}
}
@@ -561,22 +567,20 @@ const keyboard_event = evt => {
on:prev={() => { if (list_navigator) { list_navigator.prev() }}}
on:next={() => { if (list_navigator) { list_navigator.next() }}}
on:loading={e => {loading = e.detail}}
on:reload={reload}>
</FilePreview>
on:reload={reload}
/>
{:else if view === "gallery"}
<GalleryView
list={list}
on:reload={reload}
on:loading={e => {loading = e.detail}}>
</GalleryView>
on:update_list={e => list_updater.update(e.detail)}
on:pick_files={() => list_updater.pick_files()}
on:upload_files={e => list_updater.upload_files(e.detail)}
/>
{/if}
</div>
<Sharebar bind:this={sharebar}></Sharebar>
<!-- {#if ads_enabled}
<AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper>
{/if} -->
</div>
{#if ads_enabled}
@@ -611,6 +615,15 @@ const keyboard_event = evt => {
{/if}
<Downloader bind:this={downloader} file={file} list={list}></Downloader>
{#if is_list && list.can_edit}
<ListUpdater
bind:this={list_updater}
list={list}
on:reload={reload}
on:loading={e => {loading = e.detail}}
/>
{/if}
</div>
<style>

View File

@@ -6,74 +6,28 @@ import { file_type } from "./FileUtilities.svelte";
let dispatch = createEventDispatcher()
export let list = {
id: "",
title: "",
files: [],
download_href: "",
info_href: "",
can_edit: false,
}
let file_picker;
const update_list = async new_files => {
dispatch("loading", true)
// If the list is empty we simply delete it
if (list.files.length === 0) {
try {
let resp = await fetch(list.info_href, {method: "DELETE"})
if (resp.status >= 400) {
throw (await resp.json()).message
}
window.close()
} catch (err) {
alert("Failed to delete album: "+err)
} finally {
dispatch("loading", false)
}
return
}
let listjson = {
title: list.title,
files: [],
}
list.files.forEach(f => {
listjson.files.push({
id: f.id,
})
})
try {
const resp = await fetch(
list.info_href,
{ method: "PUT", body: JSON.stringify(listjson) },
);
if (resp.status >= 400) {
throw (await resp.json()).message
}
} catch (err) {
alert("Failed to update album: "+err)
} finally {
dispatch("loading", false)
}
}
const add_files = async files => {
let list_files = list.files;
files.forEach(f => {
list_files.push(f)
})
await update_list(list_files)
dispatch("reload")
list.files = list_files // Update the view (and play animation)
dispatch("update_list", list_files)
}
const delete_file = async index => {
let list_files = list.files
list_files.splice(index, 1)
await update_list(list_files)
list.files = list_files
list.files = list_files // Update the view (and play animation)
dispatch("update_list", list_files)
}
const move_left = async index => {
@@ -82,8 +36,9 @@ const move_left = async index => {
}
let f = list.files;
[f[index], f[index-1]] = [f[index-1], f[index]];
await update_list(f)
list.files = f
list.files = f // Update the view (and play animation)
dispatch("update_list", f)
}
const move_right = async index => {
if (index >= list.files.length-1) {
@@ -91,17 +46,33 @@ const move_right = async index => {
}
let f = list.files;
[f[index], f[index+1]] = [f[index+1], f[index]];
await update_list(f)
list.files = f
list.files = f // Update the view (and play animation)
dispatch("update_list", f)
}
// Index of the file which is being hovered over. -1 is nothing and -2 is the
// Add files button
let hovering = -1
let dragging = false
const drag = (e, index) => {
dragging = true
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.dropEffect = 'move';
e.dataTransfer.setData('text/plain', index);
}
const drop = (e, index) => {
hovering = -1
dragging = false
if (e.dataTransfer.files.length !== 0) {
// This is not a rearrangement, this is a file upload
dispatch("upload_files", e.dataTransfer.files)
return
} else if (index === -2) {
return
}
e.dataTransfer.dropEffect = 'move';
let start = parseInt(e.dataTransfer.getData("text/plain"));
let list_files = list.files
@@ -116,11 +87,32 @@ const drop = (e, index) => {
return; // Nothing changed
}
update_list(list_files)
dispatch("update_list", list_files)
}
</script>
<div class="gallery">
{#if list.can_edit}
<div class="add_button"
on:drop|preventDefault={e => drop(e, -2)}
on:dragover|preventDefault|stopPropagation
on:dragenter={() => hovering = -2}
on:dragend={() => {hovering = -1}}
class:highlight={!dragging && hovering === -2}
>
<button on:click={e => dispatch("pick_files")} style="font-size: 1.5em; cursor: pointer;">
<i class="icon">cloud_upload</i>
<br/>
Upload files
</button>
<button on:click={file_picker.open} style="font-size: 1.5em; cursor: pointer;">
<i class="icon">add</i>
<br/>
Add files
</button>
</div>
{/if}
{#each list.files as file, index (file)}
<a
href="#item={index}"
@@ -129,10 +121,10 @@ const drop = (e, index) => {
on:dragstart={e => drag(e, index)}
on:drop|preventDefault={e => drop(e, index)}
on:dragover|preventDefault|stopPropagation
on:dragenter={() => {hovering = index}}
on:dragend={() => {hovering = -1}}
class:highlight={hovering === index}
animate:flip={{duration: 500}}>
on:dragenter={() => hovering = index}
on:dragend={() => {hovering = -1; dragging = false}}
class:highlight={dragging && hovering === index}
animate:flip={{duration: 400}}>
<div
class="icon_container"
class:editing={list.can_edit}
@@ -159,14 +151,6 @@ const drop = (e, index) => {
{file.name}
</a>
{/each}
{#if list.can_edit}
<button class="file" on:click={file_picker.open} style="font-size: 1.5em; cursor: pointer;">
<i class="icon">add</i>
<br/>
Add files
</button>
{/if}
</div>
<FilePicker
@@ -242,4 +226,23 @@ const drop = (e, index) => {
.button_row>.separator {
flex: 1 1 auto;
}
.add_button{
width: 200px;
max-width: 42%;
height: 200px;
margin: 8px;
border-radius: 8px;
background: var(--body_color);
text-align: center;
line-height: 1.2em;
display: inline-block;
vertical-align: top;
color: var(--body_text_color);
display: flex;
flex-direction: column;
}
.add_button > * {
flex: 1 1 auto;
}
</style>

View File

@@ -0,0 +1,82 @@
<script>
import { createEventDispatcher } from "svelte";
import UploadWidget from "../util/upload_widget/UploadWidget.svelte";
let dispatch = createEventDispatcher()
export let list = {
title: "",
files: [],
info_href: "",
}
export const update = async new_files => {
dispatch("loading", true)
// If the list is empty we simply delete it
if (list.files.length === 0) {
try {
let resp = await fetch(list.info_href, {method: "DELETE"})
if (resp.status >= 400) {
throw (await resp.json()).message
}
window.close()
} catch (err) {
alert("Failed to delete album: "+err)
} finally {
dispatch("loading", false)
}
return
}
let listjson = {
title: list.title,
files: [],
}
new_files.forEach(f => {
listjson.files.push({
id: f.id,
})
})
try {
const resp = await fetch(
list.info_href,
{ method: "PUT", body: JSON.stringify(listjson) },
);
if (resp.status >= 400) {
throw (await resp.json()).message
}
} catch (err) {
alert("Failed to update album: "+err)
} finally {
dispatch("loading", false)
dispatch("reload")
}
}
let upload_widget
export const pick_files = () => upload_widget.pick_files()
export const upload_files = files => upload_widget.upload_files(files)
const uploads_finished = async (file_ids) => {
let list_files = list.files;
file_ids.forEach(id => {
list_files.push({id: id})
})
await update(list_files)
}
const paste = (e) => {
if (e.clipboardData.files.length !== 0) {
e.preventDefault();
e.stopPropagation();
upload_widget.upload_files(e.clipboardData.files)
}
}
</script>
<svelte:window on:paste={paste}/>
<UploadWidget bind:this={upload_widget} on:uploads_finished={e => uploads_finished(e.detail)}/>

View File

@@ -257,8 +257,6 @@ onMount(() => {
<svelte:window on:keydown={keydown} on:hashchange={hashChange} />
<UploadWidget bind:this={upload_widget} drop_upload on:uploads_finished={hashChange}/>
<div id="file_manager" class="file_manager">
<div id="nav_bar" class="nav_bar">
<button id="btn_menu" onclick="toggleMenu()"><i class="icon">menu</i></button>
@@ -340,6 +338,8 @@ onMount(() => {
<iframe bind:this={downloadFrame} title="File download frame" style="display: none; width: 1px; height: 1px;"></iframe>
</div>
<UploadWidget bind:this={upload_widget} drop_upload on:uploads_finished={hashChange}/>
<style>
:global(#page_body) {
height: 100vh;

View File

@@ -14,10 +14,10 @@ const drop = (e) => {
}
}
const paste = (e) => {
if (e.clipboardData.files[0]) {
if (e.clipboardData.files.length !== 0) {
e.preventDefault();
e.stopPropagation();
dispatch("upload", e.dataTransfer.files)
dispatch("upload", e.clipboardData.files)
}
}
</script>
@@ -39,8 +39,6 @@ const paste = (e) => {
<style>
.drag_target {
position: fixed;
height: auto;
margin: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

View File

@@ -1,5 +1,6 @@
<script>
import { createEventDispatcher } from "svelte";
import { fade } from "svelte/transition";
import ProgressBar from "../ProgressBar.svelte";
import { upload_file } from "./UploadFunc";
@@ -43,7 +44,7 @@ export const start = () => {
</script>
<div class="upload_progress" class:error={job.status === "error"}>
<div class="upload_progress" transition:fade={{duration: 200}} class:error={job.status === "error"}>
{job.name}<br/>
{#if error_code !== ""}
{error_message}<br/>

View File

@@ -1,5 +1,6 @@
<script>
import { createEventDispatcher, tick } from "svelte";
import { fade } from "svelte/transition";
import DropUpload from "./DropUpload.svelte";
import UploadProgress from "./UploadProgress.svelte";
let dispatch = createEventDispatcher()
@@ -103,12 +104,8 @@ const finish_upload = (e) => {
class="upload_input" type="file" name="file" multiple="multiple"
/>
{#if drop_upload}
<DropUpload on:upload={e => upload_files(e.detail)}/>
{/if}
{#if visible}
<div class="upload_widget">
<div class="upload_widget" transition:fade={{duration: 200}}>
<div class="header">
{#if state === "idle"}
Waiting for files
@@ -128,10 +125,14 @@ const finish_upload = (e) => {
</div>
{/if}
{#if drop_upload}
<DropUpload on:upload={e => upload_files(e.detail)}/>
{/if}
<style>
.upload_input {
visibility: hidden;
position: static;
position: fixed;
width: 0;
height: 0;
}
@@ -139,16 +140,15 @@ const finish_upload = (e) => {
position: fixed;
display: flex;
flex-direction: column;
width: 400px;
max-width: 90%;
width: 500px;
max-width: 80%;
height: auto;
max-height: 500px;
max-height: 50%;
right: 20px;
bottom: 20px;
border-radius: 20px 20px 8px 8px;
overflow-x: hidden;
overflow-y: auto;
box-shadow: 1px 1px 10px -2px var(--shadow_color);
overflow: hidden;
box-shadow: 1px 1px 8px var(--shadow_color);
}
.header {
@@ -166,5 +166,7 @@ const finish_upload = (e) => {
flex: 1 1 auto;
background: var(--body_color);
color: var(--body_text_color);
overflow-y: auto;
text-align: left;
}
</style>