Add file picker for adding files to albums

This commit is contained in:
2021-11-27 16:42:41 +01:00
parent b7038be202
commit 5701b083c3
10 changed files with 240 additions and 77 deletions

View File

@@ -5,7 +5,7 @@
<hr /> <hr />
{{if .Authenticated}}<a href="/user">{{.User.Username}}</a> {{if .Authenticated}}<a href="/user">{{.User.Username}}</a>
<a href="/user/filemanager#files">My Files</a> <a href="/user/filemanager#files">My Files</a>
<a href="/user/filemanager#lists">My Lists</a> <a href="/user/filemanager#lists">My Albums</a>
{{if .User.IsAdmin}} {{if .User.IsAdmin}}
<a href="/user/buckets">Buckets</a> <a href="/user/buckets">Buckets</a>
<a href="/admin">Admin Panel</a> <a href="/admin">Admin Panel</a>
@@ -28,19 +28,23 @@
{{end}} {{end}}
</div> </div>
<script> <script>
function toggleMenu() { function toggleMenu() {
var nav = document.getElementById("page_navigation"); var nav = document.getElementById("page_navigation");
var body = document.getElementById("page_body"); var body = document.getElementById("page_body");
if (nav.offsetLeft === 0) { if (nav.offsetLeft === 0) {
// Menu is visible, hide it // Menu is visible, hide it
nav.style.left = -nav.offsetWidth + "px"; nav.style.left = -nav.offsetWidth + "px";
body.style.left = "0"; body.style.left = "0";
} else { } else {
// Menu is hidden, show it // Menu is hidden, show it
nav.style.left = "0"; nav.style.left = "0";
body.style.left = nav.offsetWidth + "px"; body.style.left = nav.offsetWidth + "px";
}
} }
}
function resetMenu() {
document.getElementById("page_navigation").style.left = "";
document.getElementById("page_body").style.left = "";
}
</script> </script>
{{end}} {{end}}

View File

@@ -136,8 +136,10 @@ let update_charts = () => {
</p> </p>
<h3>About</h3> <h3>About</h3>
Pixeldrain is a file sharing platform. <p>
<a href="/" target="_blank">Visit the home page for more information.</a> Pixeldrain is a file sharing platform.
<a href="/" target="_blank">Visit the home page for more information.</a>
</p>
<h3>Keyboard Controls</h3> <h3>Keyboard Controls</h3>
<table style="max-width: 100%;"> <table style="max-width: 100%;">

View File

@@ -55,9 +55,9 @@ const example = () => {
file will also show advertisements. file will also show advertisements.
</p> </p>
<h3>Code</h3> <h3>Code</h3>
<textarea bind:value={embed_html} style="width: 100%; height: 4em; margin: 0;"></textarea> <textarea bind:value={embed_html} style="width: 100%; height: 4em;" class="indent"></textarea>
<br/> <br/>
<button on:click={copy} class:button_highlight={copy_status === "success"} class:button_red={copy_status === "error"}> <button on:click={copy} class="indent" class:button_highlight={copy_status === "success"} class:button_red={copy_status === "error"}>
<i class="icon">content_copy</i> <i class="icon">content_copy</i>
{#if copy_status === "success"} {#if copy_status === "success"}
Copied! Copied!

View File

@@ -0,0 +1,137 @@
<script>
import { createEventDispatcher, tick } from "svelte";
import { formatDataVolume } from "../util/Formatting.svelte";
import DirectoryElement from "../user_file_manager/DirectoryElement.svelte";
import Modal from "../util/Modal.svelte";
let dispatch = createEventDispatcher()
let modal;
let directory_element;
let input_search;
export const open = async () => {
modal.show()
await tick()
directory_element.setSelectionMode(true)
get_files()
}
let get_files = () => {
fetch(window.api_endpoint + "/user/files").then(resp => {
if (!resp.ok) { Promise.reject("yo") }
return resp.json()
}).then(resp => {
directory_element.reset()
for (let i in resp.files) {
directory_element.addFile(
resp.files[i].id,
window.api_endpoint + "/file/" + resp.files[i].id + "/thumbnail?width=32&height=32",
resp.files[i].name,
"/u/" + resp.files[i].id,
resp.files[i].mime_type,
resp.files[i].size,
formatDataVolume(resp.files[i].size, 4),
resp.files[i].date_upload,
)
}
directory_element.renderFiles()
}).catch((err) => {
throw (err)
})
}
const search = (e) => {
if (e.keyCode === 27) { // Escape
e.preventDefault()
input_search.value = ""
input_search.blur()
}
requestAnimationFrame(() => {
directory_element.search(input_search.value)
})
}
let done = () => {
dispatch("files", directory_element.getSelectedFiles())
modal.hide()
}
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
}
if (e.key === "/") {
e.preventDefault()
input_search.focus()
}
}
</script>
<svelte:window on:keydown={keydown} />
<Modal bind:this={modal} width="1400px" height="1200px">
<div class="header" slot="title">
<button class="button round" on:click={modal.hide}>
<i class="icon">close</i> Cancel
</button>
<div class="title">Select files to add to album</div>
<input bind:this={input_search} on:keyup={search} class="search" type="text" placeholder="press / to search"/>
<button class="button button_highlight round" on:click={done}>
<i class="icon">done</i> Add
</button>
</div>
<div class="dir_container">
<DirectoryElement bind:this={directory_element}></DirectoryElement>
</div>
</Modal>
<style>
.dir_container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 1.2em;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.title {
flex-grow: 1;
flex-shrink: 1;
}
@media(max-width: 800px) {
.title {
/* display: none; */
content: "";
}
}
.search {
min-width: 100px;
max-width: 300px;
flex-grow: 1;
flex-shrink: 1;
}
.button {
flex-grow: 0;
flex-shrink: 0;
}
</style>

View File

@@ -1,3 +1,39 @@
<script context="module">
export const file_struct = {
id: "",
name: "",
size: 0,
bandwidth_used: 0,
bandwidth_used_paid: 0,
downloads: 0,
views: 0,
mime_type: "",
availability: "",
abuse_type: "",
show_ads: false,
can_edit: false,
get_href: "",
info_href: "",
download_href: "",
icon_href: "",
}
export const list_struct = {
id: "",
title: "",
files: [],
download_href: "",
info_href: "",
can_edit: false,
}
export const file_set_href = f => {
f.get_href = window.api_endpoint+"/file/"+f.id
f.info_href = window.api_endpoint+"/file/"+f.id+"/info"
f.download_href = window.api_endpoint+"/file/"+f.id+"?download"
f.icon_href = window.api_endpoint+"/file/"+f.id+"/thumbnail"
f.timeseries_href = window.api_endpoint+"/file/"+f.id+"/timeseries"
}
</script>
<script> <script>
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { copy_text } from "../util/Util.svelte"; import { copy_text } from "../util/Util.svelte";
@@ -19,33 +55,6 @@ import GalleryView from "./GalleryView.svelte";
import Spinner from "../util/Spinner.svelte"; import Spinner from "../util/Spinner.svelte";
import Downloader from "./Downloader.svelte"; import Downloader from "./Downloader.svelte";
const file_struct = {
id: "",
name: "",
size: 0,
bandwidth_used: 0,
bandwidth_used_paid: 0,
downloads: 0,
views: 0,
mime_type: "",
availability: "",
abuse_type: "",
show_ads: false,
can_edit: false,
get_href: "",
info_href: "",
download_href: "",
icon_href: "",
}
const list_struct = {
id: "",
title: "",
files: [],
download_href: "",
info_href: "",
can_edit: false,
}
let loading = true let loading = true
let embedded = false let embedded = false
let view_token = "" let view_token = ""
@@ -209,13 +218,6 @@ const open_file_index = async index => {
body: "token=" + view_token body: "token=" + view_token
}) })
} }
const file_set_href = f => {
f.get_href = window.api_endpoint+"/file/"+f.id
f.info_href = window.api_endpoint+"/file/"+f.id+"/info"
f.download_href = window.api_endpoint+"/file/"+f.id+"?download"
f.icon_href = window.api_endpoint+"/file/"+f.id+"/thumbnail"
f.timeseries_href = window.api_endpoint+"/file/"+f.id+"/timeseries"
}
const toggle_gallery = () => { const toggle_gallery = () => {
if (view === "gallery") { if (view === "gallery") {
open_file_index(0) open_file_index(0)
@@ -479,8 +481,6 @@ const keyboard_event = evt => {
<br/> <br/>
</div></div></div> </div></div></div>
<Sharebar bind:this={sharebar}></Sharebar>
<div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible> <div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible>
{#if view === "file"} {#if view === "file"}
<FilePreview <FilePreview
@@ -499,6 +499,8 @@ const keyboard_event = evt => {
{/if} {/if}
</div> </div>
<Sharebar bind:this={sharebar}></Sharebar>
{#if ads_enabled} {#if ads_enabled}
<AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper> <AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper>
{/if} {/if}
@@ -568,7 +570,6 @@ const keyboard_event = evt => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
text-align: left; text-align: left;
z-index: 10;
box-shadow: none; box-shadow: none;
padding: 4px; padding: 4px;
} }
@@ -616,7 +617,6 @@ const keyboard_event = evt => {
width: auto; width: auto;
height: auto; height: auto;
margin: 0; margin: 0;
z-index: 9;
} }
.file_preview { .file_preview {
position: absolute; position: absolute;
@@ -639,7 +639,6 @@ const keyboard_event = evt => {
.toolbar { .toolbar {
position: absolute; position: absolute;
width: 8em; width: 8em;
z-index: 49;
overflow: hidden; overflow: hidden;
left: -8em; left: -8em;
bottom: 0; bottom: 0;
@@ -648,6 +647,7 @@ const keyboard_event = evt => {
text-align: left; text-align: left;
transition: left 0.5s, right 0.5s; transition: left 0.5s, right 0.5s;
background-color: var(--layer_2_color); background-color: var(--layer_2_color);
z-index: 1;
} }
.toolbar.toolbar_visible { left: 0; } .toolbar.toolbar_visible { left: 0; }
.file_preview.toolbar_visible { left: 8em; } .file_preview.toolbar_visible { left: 8em; }

View File

@@ -1,6 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import FilePicker from "./FilePicker.svelte"
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
export let list = { export let list = {
@@ -12,6 +13,8 @@ export let list = {
can_edit: false, can_edit: false,
} }
let file_picker;
const click_file = index => { const click_file = index => {
dispatch("set_file", index) dispatch("set_file", index)
} }
@@ -19,8 +22,6 @@ const click_file = index => {
const update_list = async new_files => { const update_list = async new_files => {
dispatch("loading", true) dispatch("loading", true)
list.files = new_files
// If the list is empty we simply delete it // If the list is empty we simply delete it
if (list.files.length === 0) { if (list.files.length === 0) {
try { try {
@@ -62,27 +63,39 @@ const update_list = async new_files => {
} }
} }
const delete_file = async index => { const add_files = async files => {
let list_files = list.files let list_files = list.files;
list_files.splice(index, 1) files.forEach(f => {
update_list(list_files) list_files.push(f)
})
await update_list(list_files)
dispatch("reload")
} }
const move_left = index => { const delete_file = async index => {
const list_files = list.files
list_files.splice(index, 1)
await update_list(list_files)
list.files = list_files
}
const move_left = async index => {
if (index === 0) { if (index === 0) {
return; return;
} }
let f = list.files; const f = list.files;
[f[index], f[index-1]] = [f[index-1], f[index]]; [f[index], f[index-1]] = [f[index-1], f[index]];
update_list(f); await update_list(f)
list.files = f
} }
const move_right = index => { const move_right = async index => {
if (index >= list.files.length-1) { if (index >= list.files.length-1) {
return; return;
} }
let f = list.files; const f = list.files;
[f[index], f[index+1]] = [f[index+1], f[index]]; [f[index], f[index+1]] = [f[index+1], f[index]];
update_list(f); await update_list(f)
list.files = f
} }
let hovering = -1 let hovering = -1
@@ -99,18 +112,19 @@ const drop = (e, index) => {
if (start < index) { if (start < index) {
list_files.splice(index + 1, 0, list_files[start]); list_files.splice(index + 1, 0, list_files[start]);
list_files.splice(start, 1); list_files.splice(start, 1);
} else { } else if (start > index) {
list_files.splice(index, 0, list_files[start]); list_files.splice(index, 0, list_files[start]);
list_files.splice(start + 1, 1); list_files.splice(start + 1, 1);
} else {
return; // Nothing changed
} }
hovering = -1
update_list(list_files) update_list(list_files)
} }
</script> </script>
<div class="gallery"> <div class="gallery">
{#each list.files as file, index (file.id)} {#each list.files as file, index (file)}
<div <div
class="file" class="file"
on:click={() => {click_file(index)}} on:click={() => {click_file(index)}}
@@ -119,6 +133,7 @@ const drop = (e, index) => {
on:drop|preventDefault={e => drop(e, index)} on:drop|preventDefault={e => drop(e, index)}
on:dragover|preventDefault|stopPropagation on:dragover|preventDefault|stopPropagation
on:dragenter={() => {hovering = index}} on:dragenter={() => {hovering = index}}
on:dragend={() => {hovering = -1}}
class:highlight={hovering === index} class:highlight={hovering === index}
animate:flip={{duration: 500}}> animate:flip={{duration: 500}}>
<div <div
@@ -136,13 +151,15 @@ const drop = (e, index) => {
</div> </div>
{/each} {/each}
<!-- {#if list.can_edit} {#if list.can_edit}
<div class="file" style="font-size: 1.5em; padding-top: 2.5em; cursor: pointer;"> <div class="file" on:click={file_picker.open} style="font-size: 1.5em; padding-top: 2.5em; cursor: pointer;">
<i class="icon">add</i> <i class="icon">add</i>
<br/> <br/>
Add files Add files
</div> </div>
{/if} --> {/if}
<FilePicker bind:this={file_picker} on:files={e => {add_files(e.detail)}}></FilePicker>
</div> </div>
<style> <style>

View File

@@ -74,7 +74,6 @@ const share_tumblr = () => {
background-color: var(--layer_1_color); background-color: var(--layer_1_color);
border-radius: 16px; border-radius: 16px;
text-align: center; text-align: center;
z-index: 48;
overflow: hidden; overflow: hidden;
transition: left 0.5s; transition: left 0.5s;
} }

View File

@@ -82,7 +82,6 @@ export const search = (term) => {
allFiles[i].filtered = false allFiles[i].filtered = false
} else { } else {
allFiles[i].filtered = true allFiles[i].filtered = true
allFiles[i].selected = false
} }
} }

View File

@@ -97,10 +97,12 @@ let hashChange = () => {
contentType = "lists" contentType = "lists"
document.title = "My Lists" document.title = "My Lists"
getUserLists() getUserLists()
resetMenu()
} else { } else {
contentType = "files" contentType = "files"
document.title = "My Files" document.title = "My Files"
getUserFiles() getUserFiles()
resetMenu()
} }
} }

View File

@@ -32,8 +32,12 @@ export const set_visible = vis => {
} }
const keydown = e => { const keydown = e => {
if (document.activeElement.type && document.activeElement.type === "text") {
return // Prevent shortcuts from interfering with input fields
}
if (e.key === 'Escape') { if (e.key === 'Escape') {
set_visible(false); set_visible(false);
e.preventDefault()
return; return;
} }
}; };
@@ -114,6 +118,5 @@ const keydown = e => {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
overflow: auto; overflow: auto;
padding: 10px;
} }
</style> </style>