diff --git a/res/include/style/layout.css b/res/include/style/layout.css index 5159d36..58a9193 100644 --- a/res/include/style/layout.css +++ b/res/include/style/layout.css @@ -454,8 +454,11 @@ input[type="submit"]:disabled, input[type="submit"].disabled, input[type="button"]:disabled, input[type="button"].disabled, input[type="color"]:disabled, input[type="color"].disabled, select:disabled , select.disabled { - background: var(--input_color_dark); + background: var(--layer_1_color); + color: var(--input_color); box-shadow: none; + transition: none; + padding: 4px 5px 4px 5px; } /* Dropdown list of the select tag */ diff --git a/res/template/account/user_buckets.html b/res/template/account/user_buckets.html new file mode 100644 index 0000000..f0f55ea --- /dev/null +++ b/res/template/account/user_buckets.html @@ -0,0 +1,21 @@ +{{define "user_buckets"}} + + + {{template "meta_tags" "Buckets"}} + {{template "user_style" .}} + + + + + {{template "page_top" .}} +

My Buckets

+
+ + {{template "page_bottom" .}} + {{template "analytics"}} + + + + + +{{end}} diff --git a/res/template/admin.html b/res/template/admin.html index 37e5bb2..a891853 100644 --- a/res/template/admin.html +++ b/res/template/admin.html @@ -17,9 +17,9 @@
- + - + diff --git a/svelte/rollup.config.js b/svelte/rollup.config.js index a6afe24..63abd25 100644 --- a/svelte/rollup.config.js +++ b/svelte/rollup.config.js @@ -31,6 +31,7 @@ const builddir = "../res/static/svelte" export default [ "filesystem", "modal", + "user_buckets", ].map((name, index) => ({ input: `src/${name}.js`, output: { diff --git a/svelte/src/filesystem/Filesystem.svelte b/svelte/src/filesystem/Filesystem.svelte index 28ad2c0..63be476 100644 --- a/svelte/src/filesystem/Filesystem.svelte +++ b/svelte/src/filesystem/Filesystem.svelte @@ -9,6 +9,8 @@ import FileManager from './filemanager/FileManager.svelte'; import Audio from './viewers/Audio.svelte'; import Image from './viewers/Image.svelte'; import Video from './viewers/Video.svelte'; +import PDF from './viewers/PDF.svelte'; +import PixeldrainLogo from '../util/PixeldrainLogo.svelte'; // Elements let file_viewer @@ -18,12 +20,18 @@ let toolbar_visible = (window.innerWidth > 600) let toolbar_toggle = () => { toolbar_visible = !toolbar_visible if (!toolbar_visible) { - sharebar.setVisible(false) + sharebar_visible = false } } let sharebar let sharebar_visible = false +$: { + if (typeof(sharebar) !== "undefined") { + sharebar.setVisible(sharebar_visible) + } +} + let details let details_visible = false let download_frame @@ -34,16 +42,21 @@ let state = { parents: initialNode.parents, base: initialNode.base, - // When navigating into a file or directory the siblings array will be - // populated with the previous base's children - siblings: [], - current_sibling: -1, + // These are used to navigate forward and backward within a directory (using + // the previous and next buttons on the toolbar). The cached siblings will + // be used so that we don't need to make an extra request to the parent + // directory. The siblings_path variable is used to verify that the parent + // directory is still the same. If it's sifferent the siblings array is not + // used + siblings_path: "", + siblings: null, // Root path of the bucket. Used for navigation by prepending it to a file // path path_root: "/d/"+initialNode.bucket.id, loading: true, - viewer_type: "" + viewer_type: "", + shuffle: false, } // Tallys @@ -51,28 +64,6 @@ $: total_directories = state.base.children.reduce((acc, cur) => cur.type === "di $: total_files = state.base.children.reduce((acc, cur) => cur.type === "file" ? acc + 1 : acc, 0) $: total_file_size = state.base.children.reduce((acc, cur) => acc + cur.file_size, 0) -const navigate = (path, pushHist) => { - state.loading = true - - fs_get_node( - state.bucket.id, path, - ).then(resp => { - window.document.title = resp.base.name+" ~ pixeldrain" - if (pushHist) { - window.history.pushState( - {}, window.document.title, "/d/"+resp.bucket.id+resp.base.path, - ) - } - - openNode(resp) - }).catch(err => { - console.error(err) - alert(err) - }).finally(() => { - state.loading = false - }) -} - const sort_children = children => { children.sort((a, b) => { // Sort directories before files @@ -83,25 +74,37 @@ const sort_children = children => { }) } -const openNode = (node) => { - // Sort directory children - sort_children(node.base.children) +const navigate = (path, pushHist) => { + state.loading = true + fs_get_node(state.bucket.id, path).then(resp => { + window.document.title = resp.base.name+" ~ pixeldrain" + if (pushHist) { + window.history.pushState( + {}, window.document.title, "/d/"+resp.bucket.id+resp.base.path, + ) + } + + // Sort directory children + sort_children(resp.base.children) + + open_node(resp) + }).catch(err => { + console.error(err) + alert(err) + }).finally(() => { + state.loading = false + }) +} + +const open_node = (node) => { // If the new node is a child of the previous node we save the parent's // children array if (node.parents.length > 0 && node.parents[node.parents.length-1].path === state.base.path) { console.debug("Current parent path and new node path match. Saving siblings") - state.siblings = state.base.children - state.current_sibling = -1 - // Find which sibling is currently open - for (let i = 0; i < state.siblings.length; i++) { - if (state.siblings[i].name === node.base.name) { - state.current_sibling = i - console.debug("Current sibling ID is", i) - break - } - } + state.siblings_path = node.parents[node.parents.length-1].path + state.siblings = state.base.children } // Update shared state @@ -126,6 +129,11 @@ const openNode = (node) => { state.base.file_type === "application/x-matroska" ) { state.viewer_type = "video" + } else if ( + state.base.file_type === "application/pdf" || + state.base.file_type === "application/x-pdf" + ) { + state.viewer_type = "pdf" } else { state.viewer_type = "" } @@ -133,46 +141,77 @@ const openNode = (node) => { // Remove spinner state.loading = false } -onMount(() => openNode(initialNode)) +onMount(() => open_node(initialNode)) // Opens a sibling of the currently open file. The offset is relative to the // file which is currently open. Give a positive number to move forward and a // negative number to move backward -const open_sibling = offset => { +const open_sibling = async offset => { + if (state.parents.length == 0) { + return + } + state.loading = true - // Get the parent directory - fs_get_node( - state.bucket.id, state.parents[state.parents.length - 1].path, - ).then(resp => { - // Sort directory children - sort_children(resp.base.children) + // Check if we already have siblings cached + if (state.siblings != null && state.siblings_path == state.parents[state.parents.length - 1].path) { + console.debug("Using cached siblings") + } else { + console.debug("Cached siblings not available. Fetching new") + try { + let resp = await fs_get_node(state.bucket.id, state.parents[state.parents.length - 1].path) + // Sort directory children to make sure the order is consistent + sort_children(resp.base.children) + + // Save new siblings in global state + state.siblings_path = state.parents[state.parents.length - 1].path + state.siblings = resp.base.children + } catch (err) { + console.error(err) + alert(err) + state.loading = false + return + } + } + + let next_sibling = null + + if (state.shuffle) { + // Shuffle is on, pick a random sibling + for (let i = 0; i < 10; i++) { + next_sibling = state.siblings[Math.floor(Math.random()*state.siblings.length)] + + // If we selected the same sibling we already have open we try + // again. Else we break the loop + if (next_sibling.name !== state.base.name) { + break + } + } + } else { // Loop over the parent node's children to find the one which is // currently open. Then, if possible, we save the one which comes before // or after it - let next_sibling = null - for (let i = 0; i < resp.base.children.length; i++) { + for (let i = 0; i < state.siblings.length; i++) { if ( - resp.base.children[i].name === state.base.name && + state.siblings[i].name === state.base.name && i+offset >= 0 && // Prevent underflow - i+offset < resp.base.children.length // Prevent overflow + i+offset < state.siblings.length // Prevent overflow ) { - next_sibling = resp.base.children[i+offset] - console.debug("Next sibling is", next_sibling) + next_sibling = state.siblings[i+offset] + break } } + } - // If we found a sibling we open it - if (next_sibling !== null) { - navigate(next_sibling.path, true) - } - }).catch(err => { - console.error(err) - alert(err) - }).finally(() => { + // If we found a sibling we open it + if (next_sibling !== null) { + console.debug("Opening sibling", next_sibling) + navigate(next_sibling.path,true) + } else { + console.debug("No siblings found") state.loading = false - }) + } } // Capture browser back and forward navigation buttons @@ -185,24 +224,42 @@ window.onpopstate = (e) => { }; const keydown = e => { + if (e.ctrlKey || e.altKey || e.metaKey) { + return // prevent custom shortcuts from interfering with system shortcuts + } + switch (e.key) { case "Escape": hide(); - return; + break; case "i": details_window.toggle() + break; case "s": download() + break; + case "r": + state.shuffle = !state.shuffle + break; + case "a", "ArrowLeft": + open_sibling(-1) + break; + case "d", "ArrowRight": + open_sibling(1) + break; } }; const download = () => { download_frame.src = fs_get_file_url(state.bucket.id, state.base.path) + "?attach" } +const share = () => { + +} - + @@ -282,7 +355,7 @@ const download = () => { - + @@ -475,5 +548,12 @@ const download = () => { .toolbar_statistic { text-align: center; } +.button_row { + display: flex; + flex-direction: row; +} +.button_row > * { + flex: 1 1 auto; +} diff --git a/svelte/src/filesystem/FilesystemAPI.svelte b/svelte/src/filesystem/FilesystemAPI.svelte index b6034c3..d36100f 100644 --- a/svelte/src/filesystem/FilesystemAPI.svelte +++ b/svelte/src/filesystem/FilesystemAPI.svelte @@ -1,5 +1,13 @@
Share on:
- - - - -
@@ -42,4 +80,9 @@ export const toggle = () => { setVisible(!visible) } transition: left 0.5s; } .visible { left: 8em; } +.button_full_width > svg { + height: 3em; + width: 3em; + fill: currentColor; +} diff --git a/svelte/src/filesystem/filemanager/FileManager.svelte b/svelte/src/filesystem/filemanager/FileManager.svelte index fcf6483..017104a 100644 --- a/svelte/src/filesystem/filemanager/FileManager.svelte +++ b/svelte/src/filesystem/filemanager/FileManager.svelte @@ -20,8 +20,6 @@ const node_click = (index) => { dispatch("navigate", state.base.children[index].path) } else if (mode === "selecting") { state.base.children[index].fm_selected = !state.base.children[index].fm_selected - } else if (mode === "deleting") { - state.base.children[index].fm_delete = !state.base.children[index].fm_delete } } const navigate_up = () => { @@ -66,9 +64,23 @@ const node_icon = node => { return "/res/img/mime/empty.png" } -const delete_node = () => { - if (mode !== "deleting") { - mode = "deleting" +const delete_selected = () => { + if (mode !== "selecting") { + return + } + + let count = state.base.children.reduce((acc, cur) => { + if (cur.fm_selected) { + acc++ + } + return acc + }, 0) + + let confirmSingle = `Are you sure you want to delete this file? This action is irreversible.` + let confirmMulti = `Are you sure you want to delete these ${count} files? This action is irreversible.` + if (count === 0 || + (count === 1 && !confirm(confirmSingle)) || + (count > 1 && !confirm(confirmMulti))) { return } @@ -77,7 +89,7 @@ const delete_node = () => { // Save all promises with deletion requests in an array let promises = [] state.base.children.forEach(child => { - if (!child.fm_delete) { return } + if (!child.fm_selected) { return } promises.push(fs_delete_node(state.bucket.id, child.path)) }) @@ -89,53 +101,53 @@ const delete_node = () => { reload() }) } -const delete_toggle = () => { - // Turn on deletion mode if it's not already - if (mode !== "deleting") { - mode = "deleting" +const toggle_select = () => { + if (mode !== "selecting") { + mode = "selecting" return } - // Return to normal and unmark all the marked files - mode = "viewing" + // Unmark all the selected files and return to viewing mode state.base.children.forEach((child, i) => { - if (child.fm_delete) { - state.base.children[i].fm_delete = false + if (child.fm_selected) { + state.base.children[i].fm_selected = false } }) + mode = "viewing" }
- + +
{#if state.bucket.permissions.update} {/if}

- {#if mode === "deleting"} -
+ {#if mode === "selecting"} +
- Deleting files. Click a file or directory to select it for deletion. - Click confirm to delete the files. + Select files or directories by clicking on them. Then you + can choose which action to perform
- - @@ -162,8 +174,7 @@ const delete_toggle = () => { href={state.path_root+child.path} on:click|preventDefault={() => {node_click(index)}} class="node" - class:node_selected={child.fm_selected} - class:node_delete={child.fm_delete}> + class:node_selected={child.fm_selected}>
@@ -180,7 +191,6 @@ const delete_toggle = () => { diff --git a/svelte/src/filesystem/viewers/Video.svelte b/svelte/src/filesystem/viewers/Video.svelte index eb0f473..4b9095a 100644 --- a/svelte/src/filesystem/viewers/Video.svelte +++ b/svelte/src/filesystem/viewers/Video.svelte @@ -1,13 +1,42 @@

Node details

Name{state.base.name}
icon