Change filesystem navigator into a class with a svelte store implementation
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
<script>
|
||||
import { fs_encode_path } from "./FilesystemUtil";
|
||||
|
||||
export let state = {}
|
||||
export let fs_navigator
|
||||
export let nav
|
||||
</script>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
{#each state.path as node, i (node.path)}
|
||||
{#each $nav.path as node, i (node.path)}
|
||||
{@const shared = node.id !== undefined && node.id !== "me"}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(node.path)}
|
||||
class="breadcrumb button"
|
||||
class:button_highlight={state.base_index === i}
|
||||
on:click|preventDefault={() => {fs_navigator.navigate(node.path, true)}}
|
||||
class:button_highlight={$nav.base_index === i}
|
||||
on:click|preventDefault={() => {nav.navigate(node.path, true)}}
|
||||
>
|
||||
{#if node.abuse_type !== undefined}
|
||||
<i class="icon small">block</i>
|
||||
{:else if shared}
|
||||
<i class="icon small">share</i>
|
||||
{/if}
|
||||
<div class="node_name" class:base={state.base_index === i}>
|
||||
<div class="node_name" class:base={$nav.base_index === i}>
|
||||
{node.name}
|
||||
</div>
|
||||
</a>
|
||||
|
@@ -9,20 +9,20 @@ import { color_by_name } from "../util/Util.svelte";
|
||||
import { tick } from "svelte";
|
||||
import CopyButton from "../layout/CopyButton.svelte";
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
export let visible = false
|
||||
export const toggle = () => visible = !visible
|
||||
|
||||
$: visibility_change(visible)
|
||||
const visibility_change = visible => {
|
||||
if (visible) {
|
||||
update_chart(state.base, 0, 0)
|
||||
update_chart(nav.base, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
$: direct_url = window.location.origin+fs_path_url(state.base.path)
|
||||
$: share_url = generate_share_url(state.path)
|
||||
$: direct_share_url = window.location.origin+fs_path_url(generate_share_path(state.path))
|
||||
$: direct_url = window.location.origin+fs_path_url($nav.base.path)
|
||||
$: share_url = generate_share_url($nav.path)
|
||||
$: direct_share_url = window.location.origin+fs_path_url(generate_share_path($nav.path))
|
||||
|
||||
let chart
|
||||
let chart_timespan = 0
|
||||
@@ -40,7 +40,7 @@ let chart_timespans = [
|
||||
let total_downloads = 0
|
||||
let total_transfer_paid = 0
|
||||
|
||||
$: update_chart(state.base, chart_timespan, chart_interval)
|
||||
$: update_chart($nav.base, chart_timespan, chart_interval)
|
||||
let update_chart = async (base, timespan, interval) => {
|
||||
if (chart === undefined) {
|
||||
// Wait for the chart element to render, if it's not rendered already
|
||||
@@ -121,42 +121,42 @@ let update_chart = async (base, timespan, interval) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:visible={visible} title="Details" width={(state.base.type === "file" ? 1000 : 750) + "px"}>
|
||||
<Modal bind:visible={visible} title="Details" width={($nav.base.type === "file" ? 1000 : 750) + "px"}>
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{state.base.name}</td>
|
||||
<td>{$nav.base.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td>{state.base.path}</td>
|
||||
<td>{$nav.base.path}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{formatDate(state.base.created, true, true, true)}</td>
|
||||
<td>{formatDate($nav.base.created, true, true, true)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Modified</td>
|
||||
<td>{formatDate(state.base.modified, true, true, true)}</td>
|
||||
<td>{formatDate($nav.base.modified, true, true, true)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mode</td>
|
||||
<td>{state.base.mode_string}</td>
|
||||
<td>{$nav.base.mode_string}</td>
|
||||
</tr>
|
||||
{#if state.base.id}
|
||||
{#if $nav.base.id}
|
||||
<tr>
|
||||
<td>Public ID</td>
|
||||
<td><a href="/d/{state.base.id}">{state.base.id}</a></td>
|
||||
<td><a href="/d/{$nav.base.id}">{$nav.base.id}</a></td>
|
||||
</tr>
|
||||
{/if}
|
||||
{#if state.base.type === "file"}
|
||||
{#if $nav.base.type === "file"}
|
||||
<tr>
|
||||
<td>File type</td>
|
||||
<td>{state.base.file_type}</td>
|
||||
<td>{$nav.base.file_type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File size</td>
|
||||
<td>{formatDataVolume(state.base.file_size, 4)} ( {formatThousands(state.base.file_size)} B )</td>
|
||||
<td>{formatDataVolume($nav.base.file_size, 4)} ( {formatThousands($nav.base.file_size)} B )</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Downloads</td>
|
||||
@@ -167,10 +167,10 @@ let update_chart = async (base, timespan, interval) => {
|
||||
<td>
|
||||
{formatDataVolume(total_transfer_paid, 4)}
|
||||
( {formatThousands(total_transfer_paid)} B ),
|
||||
{(total_transfer_paid/state.base.file_size).toFixed(1)}x file size
|
||||
{(total_transfer_paid/$nav.base.file_size).toFixed(1)}x file size
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>SHA256 sum</td><td>{state.base.sha256_sum}</td></tr>
|
||||
<tr><td>SHA256 sum</td><td>{$nav.base.sha256_sum}</td></tr>
|
||||
{/if}
|
||||
<tr>
|
||||
<td>Direct link</td>
|
||||
@@ -197,13 +197,13 @@ let update_chart = async (base, timespan, interval) => {
|
||||
{/if}
|
||||
</table>
|
||||
|
||||
{#if state.base.type === "file"}
|
||||
{#if $nav.base.type === "file"}
|
||||
<h3 class="indent">Download statistics</h3>
|
||||
|
||||
<div class="button_bar">
|
||||
{#each chart_timespans as ts}
|
||||
<button
|
||||
on:click={() => { update_chart(state.base, ts.span, ts.interval) }}
|
||||
on:click={() => update_chart($nav.base, ts.span, ts.interval)}
|
||||
class:button_highlight={chart_timespan == ts.span}>
|
||||
{ts.label}
|
||||
</button>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import { onDestroy } from "svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
|
||||
import { fs_path_url } from "./FilesystemUtil";
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
|
||||
let loading = true
|
||||
let downloads = 0
|
||||
@@ -13,17 +13,33 @@ let error_msg = ""
|
||||
|
||||
let connected_to = ""
|
||||
|
||||
$: update_base(state.base)
|
||||
onMount(() => {
|
||||
const unsub = nav.subscribe(update_base)
|
||||
return () => {
|
||||
unsub()
|
||||
close_socket()
|
||||
}
|
||||
})
|
||||
|
||||
const update_base = async base => {
|
||||
if (connected_to === base.path) {
|
||||
let total_directories = 0
|
||||
let total_files = 0
|
||||
let total_file_size = 0
|
||||
|
||||
const update_base = async () => {
|
||||
if (!nav.initialized || connected_to === nav.base.path) {
|
||||
return
|
||||
}
|
||||
if (base.type === "dir") {
|
||||
connected_to = nav.base.path
|
||||
|
||||
// Tallys
|
||||
total_directories = nav.children.reduce((acc, cur) => cur.type === "dir" ? acc + 1 : acc, 0)
|
||||
total_files = nav.children.reduce((acc, cur) => cur.type === "file" ? acc + 1 : acc, 0)
|
||||
total_file_size = nav.children.reduce((acc, cur) => acc + cur.file_size, 0)
|
||||
|
||||
if (nav.base.type === "dir") {
|
||||
console.debug("Not opening websocket for directory")
|
||||
return
|
||||
}
|
||||
connected_to = base.path
|
||||
|
||||
// If the socket is already active we need to close it
|
||||
close_socket()
|
||||
@@ -31,10 +47,10 @@ const update_base = async base => {
|
||||
loading = true
|
||||
|
||||
let ws_endpoint = location.origin.replace(/^http/, 'ws') +
|
||||
fs_path_url(base.path).replace(/^http/, 'ws') +
|
||||
fs_path_url(nav.base.path).replace(/^http/, 'ws') +
|
||||
"?download_stats"
|
||||
|
||||
console.log("Opening socket to", ws_endpoint)
|
||||
console.log("Opening socket to", ws_endpoint, "for path", nav.base.path)
|
||||
socket = new WebSocket(ws_endpoint)
|
||||
socket.onmessage = msg => {
|
||||
let j = JSON.parse(msg.data)
|
||||
@@ -53,7 +69,7 @@ const update_base = async base => {
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (socket === null) {
|
||||
update_base(base)
|
||||
update_base(nav.base)
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
@@ -71,16 +87,9 @@ const close_socket = () => {
|
||||
socket = null
|
||||
}
|
||||
}
|
||||
|
||||
// Tallys
|
||||
$: total_directories = state.children.reduce((acc, cur) => cur.type === "dir" ? acc + 1 : acc, 0)
|
||||
$: total_files = state.children.reduce((acc, cur) => cur.type === "file" ? acc + 1 : acc, 0)
|
||||
$: total_file_size = state.children.reduce((acc, cur) => acc + cur.file_size, 0)
|
||||
|
||||
onDestroy(close_socket)
|
||||
</script>
|
||||
|
||||
{#if state.base.type === "file"}
|
||||
{#if $nav.base.type === "file"}
|
||||
{#if error_msg !== ""}
|
||||
{error_msg}
|
||||
{:else}
|
||||
@@ -101,10 +110,10 @@ onDestroy(close_socket)
|
||||
|
||||
<div class="group">
|
||||
<div class="label">Size</div>
|
||||
<div class="stat">{formatDataVolume(state.base.file_size, 3)}</div>
|
||||
<div class="stat">{formatDataVolume($nav.base.file_size, 3)}</div>
|
||||
</div>
|
||||
|
||||
{:else if state.base.type === "dir" || state.base.type === "bucket"}
|
||||
{:else if $nav.base.type === "dir" || $nav.base.type === "bucket"}
|
||||
|
||||
<div class="group">
|
||||
<div class="label">Directories</div>
|
||||
|
@@ -5,13 +5,13 @@ import EditWindow from './edit_window/EditWindow.svelte';
|
||||
import Toolbar from './Toolbar.svelte';
|
||||
import Breadcrumbs from './Breadcrumbs.svelte';
|
||||
import DetailsWindow from './DetailsWindow.svelte';
|
||||
import Navigator from './Navigator.svelte';
|
||||
import FilePreview from './viewers/FilePreview.svelte';
|
||||
import SearchView from './SearchView.svelte';
|
||||
import UploadWidget from './upload_widget/UploadWidget.svelte';
|
||||
import { fs_path_url } from './FilesystemUtil.js';
|
||||
import { branding_from_path } from './edit_window/Branding.js'
|
||||
import Menu from './Menu.svelte';
|
||||
import { Navigator } from "./Navigator.js"
|
||||
|
||||
let loading = true
|
||||
let file_viewer
|
||||
@@ -24,20 +24,25 @@ let edit_window
|
||||
let edit_visible = false
|
||||
let view = "file"
|
||||
|
||||
let fs_navigator
|
||||
let state = {
|
||||
path: window.initial_node.path,
|
||||
base_index: window.initial_node.base_index,
|
||||
children: window.initial_node.children,
|
||||
permissions: window.initial_node.permissions,
|
||||
const nav = new Navigator()
|
||||
|
||||
// Shortcuts
|
||||
base: window.initial_node.path[window.initial_node.base_index],
|
||||
onMount(() => {
|
||||
nav.open_node(window.initial_node, false)
|
||||
|
||||
shuffle: false,
|
||||
// Subscribe to navigation updates. This function returns a deconstructor
|
||||
// which we can conveniently return from our mount function as well
|
||||
return nav.subscribe(nav => {
|
||||
if (!nav.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
onMount(() => fs_navigator.open_node(window.initial_node, false))
|
||||
// Custom CSS rules for the whole viewer
|
||||
document.documentElement.style = branding_from_path(nav.path)
|
||||
|
||||
// Turn off loading spinner when the navigator is done
|
||||
loading = false
|
||||
})
|
||||
})
|
||||
|
||||
const keydown = e => {
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) {
|
||||
@@ -58,14 +63,14 @@ const keydown = e => {
|
||||
if (edit_visible) {
|
||||
edit_visible = false
|
||||
} else {
|
||||
edit_window.edit(state.base, true, "file")
|
||||
edit_window.edit(nav.base, true, "file")
|
||||
}
|
||||
break;
|
||||
case "s":
|
||||
download()
|
||||
break;
|
||||
case "r":
|
||||
state.shuffle = !state.shuffle
|
||||
nav.shuffle = !nav.shuffle
|
||||
break;
|
||||
case "/":
|
||||
case "f":
|
||||
@@ -73,11 +78,11 @@ const keydown = e => {
|
||||
break
|
||||
case "a":
|
||||
case "ArrowLeft":
|
||||
fs_navigator.open_sibling(-1)
|
||||
nav.open_sibling(-1)
|
||||
break;
|
||||
case "d":
|
||||
case "ArrowRight":
|
||||
fs_navigator.open_sibling(1)
|
||||
nav.open_sibling(1)
|
||||
break;
|
||||
case " ": // Spacebar pauses / unpauses video and audio playback
|
||||
if (file_preview) {
|
||||
@@ -94,10 +99,10 @@ const keydown = e => {
|
||||
};
|
||||
|
||||
const download = () => {
|
||||
if (state.base.type === "file") {
|
||||
download_frame.src = fs_path_url(state.base.path) + "?attach"
|
||||
} else if (state.base.type === "dir") {
|
||||
download_frame.src = fs_path_url(state.base.path) + "?bulk_download"
|
||||
if (nav.base.type === "file") {
|
||||
download_frame.src = fs_path_url(nav.base.path) + "?attach"
|
||||
} else if (nav.base.type === "dir") {
|
||||
download_frame.src = fs_path_url(nav.base.path) + "?bulk_download"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,40 +112,30 @@ const search = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.base.type !== "dir") {
|
||||
await fs_navigator.navigate(state.path[state.path.length-2].path)
|
||||
// If we are not currently in a directory, then we navigate up to the parent
|
||||
// directory
|
||||
if (nav.base.type !== "dir") {
|
||||
await nav.navigate_up()
|
||||
}
|
||||
|
||||
view = "search"
|
||||
}
|
||||
|
||||
const loading_evt = e => loading = e.detail
|
||||
|
||||
// Custom CSS rules for the whole viewer. This is updated by either the
|
||||
// navigation_complete event or the style_change event
|
||||
$: update_css(state.path)
|
||||
const update_css = path => document.documentElement.style = branding_from_path(path)
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
|
||||
<Navigator
|
||||
bind:this={fs_navigator}
|
||||
bind:state
|
||||
on:loading={loading_evt}
|
||||
/>
|
||||
|
||||
<div bind:this={file_viewer} class="file_viewer">
|
||||
<div class="headerbar">
|
||||
<Menu/>
|
||||
<Breadcrumbs state={state} fs_navigator={fs_navigator}/>
|
||||
<Breadcrumbs nav={nav}/>
|
||||
</div>
|
||||
|
||||
<div class="viewer_area">
|
||||
<Toolbar
|
||||
bind:this={toolbar}
|
||||
fs_navigator={fs_navigator}
|
||||
state={state}
|
||||
nav={nav}
|
||||
file_viewer={file_viewer}
|
||||
bind:details_visible={details_visible}
|
||||
edit_window={edit_window}
|
||||
@@ -154,18 +149,16 @@ const update_css = path => document.documentElement.style = branding_from_path(p
|
||||
{#if view === "file"}
|
||||
<FilePreview
|
||||
bind:this={file_preview}
|
||||
fs_navigator={fs_navigator}
|
||||
state={state}
|
||||
nav={nav}
|
||||
edit_window={edit_window}
|
||||
on:loading={loading_evt}
|
||||
on:open_sibling={e => fs_navigator.open_sibling(e.detail)}
|
||||
on:open_sibling={e => nav.open_sibling(e.detail)}
|
||||
on:download={download}
|
||||
on:upload_picker={() => upload_widget.pick_files()}
|
||||
/>
|
||||
{:else if view === "search"}
|
||||
<SearchView
|
||||
state={state}
|
||||
fs_navigator={fs_navigator}
|
||||
nav={nav}
|
||||
on:loading={loading_evt}
|
||||
on:done={() => {view = "file"}}
|
||||
/>
|
||||
@@ -181,25 +174,24 @@ const update_css = path => document.documentElement.style = branding_from_path(p
|
||||
</iframe>
|
||||
|
||||
<DetailsWindow
|
||||
state={state}
|
||||
nav={nav}
|
||||
bind:visible={details_visible}
|
||||
/>
|
||||
|
||||
<EditWindow
|
||||
nav={nav}
|
||||
bind:this={edit_window}
|
||||
bind:visible={edit_visible}
|
||||
fs_navigator={fs_navigator}
|
||||
on:loading={loading_evt}
|
||||
/>
|
||||
|
||||
<UploadWidget
|
||||
nav={nav}
|
||||
bind:this={upload_widget}
|
||||
fs_state={state}
|
||||
drop_upload
|
||||
on:uploads_finished={() => fs_navigator.reload()}
|
||||
/>
|
||||
|
||||
<LoadingIndicator loading={loading}/>
|
||||
<LoadingIndicator loading={$nav.loading || loading}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@@ -13,6 +13,9 @@ export const fs_encode_path = path => {
|
||||
}
|
||||
|
||||
export const fs_path_url = path => {
|
||||
if (!path || path.length === 0) {
|
||||
return ""
|
||||
}
|
||||
if (path[0] !== "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
237
svelte/src/filesystem/Navigator.js
Normal file
237
svelte/src/filesystem/Navigator.js
Normal file
@@ -0,0 +1,237 @@
|
||||
import { fs_get_node } from "./FilesystemAPI";
|
||||
import { fs_encode_path, fs_split_path } from "./FilesystemUtil";
|
||||
|
||||
export class Navigator {
|
||||
// Parts of the raw API response
|
||||
path = []
|
||||
base_index = 0
|
||||
children = []
|
||||
permissions = {}
|
||||
|
||||
// base equals path[base_index]. It's updated every time the path updates
|
||||
base = {}
|
||||
|
||||
// Initialized will be set to true when the first file or directory is loaded
|
||||
initialized = false
|
||||
|
||||
shuffle = false
|
||||
loading = false
|
||||
|
||||
// Whether navigation events should update the browser history
|
||||
history_enabled = true
|
||||
|
||||
constructor(history_enabled = true) {
|
||||
this.history_enabled = history_enabled
|
||||
|
||||
// If history logging is enabled we capture the popstate event, which
|
||||
// fires when the user uses the back and forward buttons in the browser.
|
||||
// Instead of reloading the page we use the navigator to navigate to the
|
||||
// new page
|
||||
if (history_enabled) {
|
||||
window.onpopstate = () => {
|
||||
// Get the part of the URL after the fs root and navigate to it
|
||||
const path = document.location.pathname.replace("/d/", "")
|
||||
this.navigate(decodeURIComponent(path), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Navigator acts as a svelte store. This allows for DOM reactivity.
|
||||
// This works by implementing the store contract:
|
||||
// https://svelte.dev/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values
|
||||
subscribers = []
|
||||
subscribe(sub_func) {
|
||||
// Immediately return the current value
|
||||
sub_func(this)
|
||||
|
||||
this.subscribers.push(sub_func)
|
||||
|
||||
// Return the unsubscribe function
|
||||
return () => {
|
||||
this.subscribers.splice(this.subscribers.indexOf(sub_func), 1)
|
||||
}
|
||||
}
|
||||
|
||||
async navigate(path, push_history) {
|
||||
if (path[0] !== "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
console.debug("Navigating to path", path, push_history)
|
||||
|
||||
try {
|
||||
const resp = await fs_get_node(path)
|
||||
this.open_node(resp, push_history)
|
||||
} catch (err) {
|
||||
if (err.value && err.value === "path_not_found") {
|
||||
if (path !== this.path[0].path && path !== "/" && path !== "") {
|
||||
console.debug("Path", path, "was not found, trying to navigate to parent")
|
||||
this.navigate(fs_split_path(path).parent, push_history)
|
||||
}
|
||||
} else if (err.message) {
|
||||
console.error(err)
|
||||
alert("Error: " + err.message)
|
||||
} else {
|
||||
console.error(err)
|
||||
alert("Error: " + err)
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async navigate_up() {
|
||||
if (this.path.length > 1) {
|
||||
await this.navigate(this.path[this.path.length - 2].path)
|
||||
}
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.navigate(this.base.path, false)
|
||||
}
|
||||
|
||||
open_node(node, push_history) {
|
||||
// Update window title and navigation history. If push_history is false
|
||||
// we still replace the URL with replaceState. This way the user is not
|
||||
// greeted to a 404 page when refreshing after renaming a file
|
||||
if (this.history_enabled) {
|
||||
window.document.title = node.path[node.base_index].name + " ~ pixeldrain"
|
||||
const url = "/d" + fs_encode_path(node.path[node.base_index].path)
|
||||
if (push_history) {
|
||||
window.history.pushState({}, window.document.title, url)
|
||||
} else {
|
||||
window.history.replaceState({}, window.document.title, url)
|
||||
}
|
||||
}
|
||||
|
||||
// If the new node is a child of the previous node we save the parent's
|
||||
// children array
|
||||
if (node.path.length > 1 && node.path[node.path.length - 2].path === this.base.path) {
|
||||
console.debug("Current parent path and new node path match. Saving siblings")
|
||||
|
||||
this.cached_siblings_path = node.path[node.path.length - 1].path
|
||||
this.cached_siblings = this.children
|
||||
}
|
||||
|
||||
// Sort directory children
|
||||
sort_children(node.children)
|
||||
|
||||
// Update shared state
|
||||
this.path = node.path
|
||||
this.base_index = node.base_index
|
||||
this.base = node.path[node.base_index]
|
||||
this.children = node.children
|
||||
this.permissions = node.permissions
|
||||
this.initialized = true
|
||||
|
||||
console.debug("Opened node", node)
|
||||
|
||||
// Signal to our subscribers that the new node is loaded. This triggers
|
||||
// the reactivity
|
||||
for (let i = 0; i < this.subscribers.length; i++) {
|
||||
this.subscribers[i](this)
|
||||
}
|
||||
|
||||
// Remove spinner
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
// 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 different the siblings array is not
|
||||
// used
|
||||
cached_siblings_path = ""
|
||||
cached_siblings = null
|
||||
|
||||
async get_siblings() {
|
||||
// Check if we already have siblings cached
|
||||
if (
|
||||
this.cached_siblings === null ||
|
||||
this.cached_siblings_path !== this.path[this.path.length - 2].path
|
||||
) {
|
||||
console.debug("Cached siblings not available. Fetching new")
|
||||
const resp = await fs_get_node(this.path[this.path.length - 2].path)
|
||||
|
||||
// Sort directory children to make sure the order is consistent
|
||||
sort_children(resp.children)
|
||||
|
||||
// Save new siblings in navigator state
|
||||
this.cached_siblings_path = this.path[this.path.length - 2].path
|
||||
this.cached_siblings = resp.children
|
||||
}
|
||||
|
||||
return this.cached_siblings
|
||||
}
|
||||
|
||||
// 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
|
||||
async open_sibling(offset) {
|
||||
if (this.path.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
let siblings
|
||||
try {
|
||||
siblings = await this.get_siblings()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
let next_sibling = null
|
||||
|
||||
if (this.shuffle) {
|
||||
// Shuffle is on, pick a random sibling
|
||||
for (let i = 0; i < 10; i++) {
|
||||
next_sibling = siblings[Math.floor(Math.random() * siblings.length)]
|
||||
|
||||
// If we selected the same sibling we already have open we try
|
||||
// again. Else we break the loop
|
||||
if (next_sibling.name !== this.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
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
if (
|
||||
siblings[i].name === this.base.name &&
|
||||
i + offset >= 0 && // Prevent underflow
|
||||
i + offset < siblings.length // Prevent overflow
|
||||
) {
|
||||
next_sibling = siblings[i + offset]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a sibling we open it
|
||||
if (next_sibling !== null) {
|
||||
console.debug("Opening sibling", next_sibling.path)
|
||||
this.navigate(next_sibling.path, true)
|
||||
} else {
|
||||
console.debug("No siblings found")
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sort_children = (children) => {
|
||||
children.sort((a, b) => {
|
||||
// Sort directories before files
|
||||
if (a.type !== b.type) {
|
||||
return a.type === "dir" ? -1 : 1
|
||||
}
|
||||
return a.name.localeCompare(b.name, undefined, { numeric: true })
|
||||
})
|
||||
}
|
@@ -1,202 +0,0 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fs_get_node } from "./FilesystemAPI";
|
||||
import { fs_encode_path, fs_split_path } from "./FilesystemUtil";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let history_enabled = true
|
||||
|
||||
export let state = {
|
||||
// Parts of the raw API response
|
||||
path: [],
|
||||
base_index: 0,
|
||||
children: [],
|
||||
permissions: {},
|
||||
|
||||
// The part of the path that base_index points to
|
||||
base: {},
|
||||
|
||||
shuffle: false,
|
||||
}
|
||||
|
||||
export const navigate = async (path, push_history) => {
|
||||
if (path[0] !== "/") {
|
||||
path = "/"+path
|
||||
}
|
||||
|
||||
dispatch("loading", true)
|
||||
console.debug("Navigating to path", path, push_history)
|
||||
|
||||
try {
|
||||
const resp = await fs_get_node(path)
|
||||
open_node(resp, push_history)
|
||||
} catch (err) {
|
||||
if (err.value && err.value === "path_not_found") {
|
||||
if (path !== state.path[0].path && path !== "/" && path !== "") {
|
||||
console.debug("Path", path, "was not found, trying to navigate to parent")
|
||||
navigate(fs_split_path(path).parent, push_history)
|
||||
}
|
||||
} else if (err.message) {
|
||||
console.error(err)
|
||||
alert("Error: "+err.message)
|
||||
} else {
|
||||
console.error(err)
|
||||
alert("Error: "+err)
|
||||
}
|
||||
} finally {
|
||||
dispatch("loading", false)
|
||||
}
|
||||
}
|
||||
|
||||
export const reload = () => {
|
||||
navigate(state.base.path, false)
|
||||
}
|
||||
|
||||
export const open_node = (node, push_history) => {
|
||||
// Update window title and navigation history. If push_history is false we
|
||||
// still replace the URL with replaceState. This way the user is not greeted
|
||||
// to a 404 page when refreshing after renaming a file
|
||||
if (history_enabled) {
|
||||
window.document.title = node.path[node.base_index].name+" ~ pixeldrain"
|
||||
const url = "/d"+ fs_encode_path(node.path[node.base_index].path)
|
||||
if (push_history) {
|
||||
window.history.pushState({}, window.document.title, url)
|
||||
} else {
|
||||
window.history.replaceState({}, window.document.title, url)
|
||||
}
|
||||
}
|
||||
|
||||
// If the new node is a child of the previous node we save the parent's
|
||||
// children array
|
||||
if (node.path.length > 1 && node.path[node.path.length-2].path === state.base.path) {
|
||||
console.debug("Current parent path and new node path match. Saving siblings")
|
||||
|
||||
cached_siblings_path = node.path[node.path.length-1].path
|
||||
cached_siblings = state.children
|
||||
}
|
||||
|
||||
// Sort directory children
|
||||
sort_children(node.children)
|
||||
|
||||
// Update shared state
|
||||
state.path = node.path
|
||||
state.base = node.path[node.base_index]
|
||||
state.base_index = node.base_index
|
||||
state.children = node.children
|
||||
state.permissions = node.permissions
|
||||
|
||||
console.debug("Opened node", node)
|
||||
|
||||
// Signal to parent that navigation is complete. Normally relying on
|
||||
// reactivity is enough, but sometimes that can trigger double updates. By
|
||||
// manually triggering an update we can be sure that updates happen exactly
|
||||
// when we mean to
|
||||
dispatch("navigation_complete")
|
||||
|
||||
// Remove spinner
|
||||
dispatch("loading", false)
|
||||
}
|
||||
|
||||
// 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
|
||||
let cached_siblings_path = ""
|
||||
let cached_siblings = null
|
||||
|
||||
export const get_siblings = async () => {
|
||||
// Check if we already have siblings cached
|
||||
if (cached_siblings === null || cached_siblings_path !== state.path[state.path.length - 2].path) {
|
||||
console.debug("Cached siblings not available. Fetching new")
|
||||
const resp = await fs_get_node(state.path[state.path.length - 2].path)
|
||||
|
||||
// Sort directory children to make sure the order is consistent
|
||||
sort_children(resp.children)
|
||||
|
||||
// Save new siblings in navigator state
|
||||
cached_siblings_path = state.path[state.path.length - 2].path
|
||||
cached_siblings = resp.children
|
||||
}
|
||||
|
||||
return cached_siblings
|
||||
}
|
||||
|
||||
// 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
|
||||
export const open_sibling = async offset => {
|
||||
if (state.path.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
dispatch("loading", true)
|
||||
|
||||
let siblings
|
||||
try {
|
||||
siblings = await get_siblings()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
dispatch("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 = siblings[Math.floor(Math.random()*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
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
if (
|
||||
siblings[i].name === state.base.name &&
|
||||
i+offset >= 0 && // Prevent underflow
|
||||
i+offset < siblings.length // Prevent overflow
|
||||
) {
|
||||
next_sibling = siblings[i+offset]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a sibling we open it
|
||||
if (next_sibling !== null) {
|
||||
console.debug("Opening sibling", next_sibling.path)
|
||||
navigate(next_sibling.path, true)
|
||||
} else {
|
||||
console.debug("No siblings found")
|
||||
dispatch("loading", false)
|
||||
}
|
||||
}
|
||||
|
||||
const sort_children = children => {
|
||||
children.sort((a, b) => {
|
||||
// Sort directories before files
|
||||
if (a.type !== b.type) {
|
||||
return a.type === "dir" ? -1 : 1
|
||||
}
|
||||
return a.name.localeCompare(b.name, undefined, {numeric: true})
|
||||
})
|
||||
}
|
||||
|
||||
// Capture browser back and forward navigation buttons
|
||||
window.onpopstate = (e) => {
|
||||
// Get the part of the URL after the fs root and navigate to it
|
||||
const path = document.location.pathname.replace("/d/", "")
|
||||
navigate(decodeURIComponent(path), false)
|
||||
};
|
||||
</script>
|
@@ -3,8 +3,7 @@ import { createEventDispatcher } from "svelte";
|
||||
import { fs_search } from "./FilesystemAPI";
|
||||
import { fs_encode_path, fs_thumbnail_url } from "./FilesystemUtil";
|
||||
|
||||
export let state
|
||||
export let fs_navigator
|
||||
export let nav
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
@@ -37,7 +36,7 @@ const search = async (limit = 10) => {
|
||||
dispatch("loading", true)
|
||||
|
||||
try {
|
||||
search_results = await fs_search(state.base.path, search_term, limit)
|
||||
search_results = await fs_search(nav.base.path, search_term, limit)
|
||||
} catch (err) {
|
||||
if (err.value) {
|
||||
error = err.value
|
||||
@@ -75,7 +74,7 @@ const submit_search = () => {
|
||||
}
|
||||
|
||||
const open_result = index => {
|
||||
fs_navigator.navigate(search_results[index], true)
|
||||
nav.navigate(search_results[index], true)
|
||||
dispatch("done")
|
||||
}
|
||||
</script>
|
||||
@@ -101,7 +100,7 @@ const open_result = index => {
|
||||
<input
|
||||
class="term"
|
||||
type="text"
|
||||
placeholder="Type to search in {state.base.name}"
|
||||
placeholder="Type to search in {$nav.base.name}"
|
||||
style="width: 100%;"
|
||||
bind:value={search_term}
|
||||
on:keyup={keyup}
|
||||
@@ -126,7 +125,7 @@ const open_result = index => {
|
||||
</td>
|
||||
<td class="node_name">
|
||||
<!-- Remove the search directory from the result -->
|
||||
{result.slice(state.base.path.length+1)}
|
||||
{result.slice($nav.base.path.length+1)}
|
||||
</td>
|
||||
</a>
|
||||
{/each}
|
||||
|
@@ -6,15 +6,7 @@ import FileStats from "./FileStats.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let fs_navigator
|
||||
export let state = {
|
||||
base: {
|
||||
type: "",
|
||||
path: "",
|
||||
},
|
||||
children: [],
|
||||
shuffle: false
|
||||
}
|
||||
export let nav
|
||||
|
||||
export let view = "file"
|
||||
export let details_visible = false
|
||||
@@ -22,11 +14,11 @@ export let edit_window
|
||||
export let edit_visible = false
|
||||
export let file_viewer
|
||||
|
||||
$: share_url = generate_share_url(state.path)
|
||||
$: share_url = generate_share_url($nav.path)
|
||||
let link_copied = false
|
||||
export const copy_link = () => {
|
||||
if (share_url === "") {
|
||||
edit_window.edit(state.base, true, "share")
|
||||
edit_window.edit(nav.base, true, "share")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,14 +28,14 @@ export const copy_link = () => {
|
||||
}
|
||||
let share = async () => {
|
||||
if (share_url === "") {
|
||||
edit_window.edit(state.base, true, "share")
|
||||
edit_window.edit(nav.base, true, "share")
|
||||
return
|
||||
}
|
||||
|
||||
if (navigator.share) {
|
||||
await navigator.share({
|
||||
title: state.base.name,
|
||||
text: "I would like to share '" + state.base.name + "' with you",
|
||||
title: nav.base.name,
|
||||
text: "I would like to share '" + nav.base.name + "' with you",
|
||||
url: share_url
|
||||
})
|
||||
} else {
|
||||
@@ -85,25 +77,25 @@ let expand = e => {
|
||||
<i class="icon">expand_less</i>
|
||||
{/if}
|
||||
</button>
|
||||
<FileStats state={state}/>
|
||||
<FileStats nav={nav}/>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
<div class="grid">
|
||||
|
||||
<div class="button_row">
|
||||
<button on:click={() => {fs_navigator.open_sibling(-1)}}>
|
||||
<button on:click={() => {nav.open_sibling(-1)}}>
|
||||
<i class="icon">skip_previous</i>
|
||||
</button>
|
||||
<button on:click={() => {state.shuffle = !state.shuffle}} class:button_highlight={state.shuffle}>
|
||||
<button on:click={() => {nav.shuffle = !nav.shuffle}} class:button_highlight={nav.shuffle}>
|
||||
<i class="icon">shuffle</i>
|
||||
</button>
|
||||
<button on:click={() => {fs_navigator.open_sibling(1)}}>
|
||||
<button on:click={() => {nav.open_sibling(1)}}>
|
||||
<i class="icon">skip_next</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if state.path[0].id === "me"}
|
||||
{#if $nav.path[0] && $nav.path[0].id === "me"}
|
||||
<button on:click={() => dispatch("search")} class:button_highlight={view === "search"}>
|
||||
<i class="icon">search</i>
|
||||
<span>Search</span>
|
||||
@@ -124,7 +116,7 @@ let expand = e => {
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if state.base.id !== "me"}
|
||||
{#if $nav.base.id !== "me"}
|
||||
<button on:click={share}>
|
||||
<i class="icon">share</i>
|
||||
<span>Share</span>
|
||||
@@ -151,8 +143,8 @@ let expand = e => {
|
||||
<span>Deta<u>i</u>ls</span>
|
||||
</button>
|
||||
|
||||
{#if state.base.id !== "me" && state.permissions.update === true}
|
||||
<button on:click={() => edit_window.edit(state.base, true, "file")} class:button_highlight={edit_visible}>
|
||||
{#if $nav.base.id !== "me" && $nav.permissions.update === true}
|
||||
<button on:click={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
|
||||
<i class="icon">edit</i>
|
||||
<span><u>E</u>dit</span>
|
||||
</button>
|
||||
|
@@ -9,7 +9,7 @@ import SharingOptions from "./SharingOptions.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let fs_navigator
|
||||
export let nav
|
||||
let file = {
|
||||
path: "",
|
||||
name: "",
|
||||
@@ -117,9 +117,9 @@ const save = async (keep_editing = false) => {
|
||||
}
|
||||
|
||||
if (open_after_edit) {
|
||||
fs_navigator.navigate(file.path, false)
|
||||
nav.navigate(file.path, false)
|
||||
} else {
|
||||
fs_navigator.reload()
|
||||
nav.reload()
|
||||
}
|
||||
|
||||
if (keep_editing) {
|
||||
@@ -149,7 +149,7 @@ const save = async (keep_editing = false) => {
|
||||
<div class="tab_content">
|
||||
{#if tab === "file"}
|
||||
<FileOptions
|
||||
fs_navigator={fs_navigator}
|
||||
nav={nav}
|
||||
bind:file
|
||||
bind:new_name
|
||||
bind:visible
|
||||
|
@@ -5,7 +5,7 @@ import { fs_delete_all } from "../FilesystemAPI";
|
||||
import PathLink from "../util/PathLink.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
export let fs_navigator
|
||||
export let nav
|
||||
export let file = {}
|
||||
export let new_name
|
||||
export let visible
|
||||
@@ -28,9 +28,9 @@ const delete_file = async e => {
|
||||
}
|
||||
|
||||
if (open_after_edit) {
|
||||
fs_navigator.navigate(file.path, false)
|
||||
nav.navigate(file.path, false)
|
||||
} else {
|
||||
fs_navigator.reload()
|
||||
nav.reload()
|
||||
}
|
||||
visible = false
|
||||
}
|
||||
@@ -42,7 +42,7 @@ const delete_file = async e => {
|
||||
<div class="highlight_yellow">
|
||||
Filesystem root cannot be renamed. If this shared directory
|
||||
is in
|
||||
<PathLink nav={fs_navigator} path="/me">your filesystem</PathLink>
|
||||
<PathLink nav={nav} path="/me">your filesystem</PathLink>
|
||||
you can rename it from there
|
||||
</div>
|
||||
{/if}
|
||||
|
@@ -4,31 +4,44 @@ import { fs_mkdir } from "../FilesystemAPI.js";
|
||||
import Button from "../../layout/Button.svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state;
|
||||
export let nav;
|
||||
|
||||
let name_input;
|
||||
let new_dir_name = ""
|
||||
let create_dir = () => {
|
||||
let error_msg = ""
|
||||
let create_dir = async () => {
|
||||
dispatch("loading", true)
|
||||
|
||||
let form = new FormData()
|
||||
form.append("type", "dir")
|
||||
|
||||
fs_mkdir(state.base.path+"/"+new_dir_name).then(resp => {
|
||||
try {
|
||||
await fs_mkdir(nav.base.path+"/"+new_dir_name)
|
||||
new_dir_name = "" // Clear input field
|
||||
}).catch(err => {
|
||||
if (err.value && err.value === "node_already_exists") {
|
||||
alert("A directory with this name already exists")
|
||||
} else {
|
||||
alert(err)
|
||||
}
|
||||
}).finally(() => {
|
||||
error_msg = "" // Clear error msg
|
||||
dispatch("done")
|
||||
})
|
||||
} catch (err) {
|
||||
if (err.value && err.value === "node_already_exists") {
|
||||
error_msg = "A directory with this name already exists"
|
||||
} else {
|
||||
error_msg = "Server returned an error: "+err
|
||||
}
|
||||
} finally {
|
||||
dispatch("loading", false)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => { name_input.focus() })
|
||||
onMount(() => {
|
||||
name_input.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if error_msg !== ""}
|
||||
<div class="highlight_yellow create_dir">
|
||||
{error_msg}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form id="create_dir_form" class="create_dir" on:submit|preventDefault={create_dir}>
|
||||
<img src="/res/img/mime/folder.png" class="icon" alt="icon"/>
|
||||
<input class="dirname" type="text" bind:this={name_input} bind:value={new_dir_name} />
|
||||
|
@@ -5,7 +5,7 @@ import { fs_import } from "../FilesystemAPI";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
let file_picker
|
||||
|
||||
export const open = () => file_picker.open()
|
||||
@@ -21,7 +21,7 @@ const import_files = async files => {
|
||||
})
|
||||
|
||||
try {
|
||||
await fs_import(state.base.path, fileids)
|
||||
await fs_import(nav.base.path, fileids)
|
||||
} catch (err) {
|
||||
if (err.message) {
|
||||
alert(err.message)
|
||||
|
@@ -9,8 +9,7 @@ import FileImporter from './FileImporter.svelte';
|
||||
import { formatDate } from '../../util/Formatting.svelte';
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let fs_navigator
|
||||
export let state
|
||||
export let nav
|
||||
export let edit_window
|
||||
export let directory_view = ""
|
||||
let large_icons = false
|
||||
@@ -20,7 +19,7 @@ let creating_dir = false
|
||||
let show_hidden = false
|
||||
let file_importer
|
||||
|
||||
$: selected_files = state.children.reduce((acc, file) => {
|
||||
$: selected_files = $nav.children.reduce((acc, file) => {
|
||||
if (file.fm_selected) {
|
||||
acc++
|
||||
}
|
||||
@@ -41,13 +40,13 @@ const node_click = e => {
|
||||
// We prefix our custom state properties with fm_ to not interfere with
|
||||
// other modules
|
||||
if (mode === "viewing") {
|
||||
fs_navigator.navigate(state.children[index].path, true)
|
||||
nav.navigate(nav.children[index].path, true)
|
||||
} else if (mode === "moving") {
|
||||
// If we are moving files we can only enter directories, and only if
|
||||
// they're not selected. That last requirement prevents people from
|
||||
// moving a directory into itself
|
||||
if (state.children[index].type === "dir" && !state.children[index].fm_selected) {
|
||||
fs_navigator.navigate(state.children[index].path, true)
|
||||
if (nav.children[index].type === "dir" && !nav.children[index].fm_selected) {
|
||||
nav.navigate(nav.children[index].path, true)
|
||||
}
|
||||
} else if (mode === "selecting") {
|
||||
select_node(index)
|
||||
@@ -64,25 +63,17 @@ const node_share_click = e => {
|
||||
let index = e.detail
|
||||
|
||||
creating_dir = false
|
||||
fs_navigator.navigate(state.children[index].id, true)
|
||||
nav.navigate(nav.children[index].id, true)
|
||||
}
|
||||
const node_select = e => {
|
||||
let index = e.detail
|
||||
mode = "selecting"
|
||||
state.children[index].fm_selected = !state.children[index].fm_selected
|
||||
nav.children[index].fm_selected = !nav.children[index].fm_selected
|
||||
}
|
||||
|
||||
const node_settings = e => edit_window.edit(state.children[e.detail], false, "file")
|
||||
const node_branding = e => edit_window.edit(state.children[e.detail], false, "branding")
|
||||
const node_settings = e => edit_window.edit(nav.children[e.detail], false, "file")
|
||||
const node_branding = e => edit_window.edit(nav.children[e.detail], false, "branding")
|
||||
|
||||
const navigate_up = () => {
|
||||
creating_dir = false
|
||||
|
||||
// Go to the path of the last parent
|
||||
if (state.path.length > 1) {
|
||||
fs_navigator.navigate(state.path[state.path.length-2].path, true)
|
||||
}
|
||||
}
|
||||
const navigate_back = () => {
|
||||
creating_dir = false
|
||||
history.back()
|
||||
@@ -91,7 +82,7 @@ const navigate_back = () => {
|
||||
// Deletion function
|
||||
|
||||
const delete_selected = async () => {
|
||||
let count = state.children.reduce((acc, cur) => {
|
||||
let count = nav.children.reduce((acc, cur) => {
|
||||
if (cur.fm_selected) {
|
||||
acc++
|
||||
}
|
||||
@@ -111,7 +102,7 @@ const delete_selected = async () => {
|
||||
try {
|
||||
// Save all promises with deletion requests in an array
|
||||
let promises = []
|
||||
state.children.forEach(child => {
|
||||
nav.children.forEach(child => {
|
||||
if (!child.fm_selected) { return }
|
||||
promises.push(fs_delete_all(child.path))
|
||||
})
|
||||
@@ -123,7 +114,7 @@ const delete_selected = async () => {
|
||||
alert("Delete failed: " + err.message + " ("+err.value+")")
|
||||
} finally {
|
||||
viewing_mode()
|
||||
fs_navigator.reload()
|
||||
nav.reload()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +128,9 @@ const viewing_mode = () => {
|
||||
moving_items = []
|
||||
|
||||
// Unmark all the selected files and return to viewing mode
|
||||
state.children.forEach((child, i) => {
|
||||
nav.children.forEach((child, i) => {
|
||||
if (child.fm_selected) {
|
||||
state.children[i].fm_selected = false
|
||||
nav.children[i].fm_selected = false
|
||||
}
|
||||
})
|
||||
mode = "viewing"
|
||||
@@ -181,11 +172,11 @@ const select_node = index => {
|
||||
|
||||
for (let i = id_low; i <= id_high; i++) {
|
||||
if (i != last_selected_node) {
|
||||
state.children[i].fm_selected = !state.children[i].fm_selected
|
||||
nav.children[i].fm_selected = !nav.children[i].fm_selected
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.children[index].fm_selected = !state.children[index].fm_selected
|
||||
nav.children[index].fm_selected = !nav.children[index].fm_selected
|
||||
}
|
||||
|
||||
last_selected_node = index
|
||||
@@ -194,8 +185,10 @@ const select_node = index => {
|
||||
// When the directory is reloaded we want to keep our selection, so this
|
||||
// function watches the children array for changes and updates the selection
|
||||
// when it changes
|
||||
$: update(state.children)
|
||||
$: update($nav.children)
|
||||
const update = (children) => {
|
||||
creating_dir = false
|
||||
|
||||
// Highlight the files which were previously selected
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
for (let j = 0; j < moving_items.length; j++) {
|
||||
@@ -211,7 +204,7 @@ let moving_directories = 0
|
||||
const move_start = () => {
|
||||
moving_files = 0
|
||||
moving_directories = 0
|
||||
moving_items = state.children.reduce((acc, child) => {
|
||||
moving_items = nav.children.reduce((acc, child) => {
|
||||
if (child.fm_selected) {
|
||||
if (child.type === "file") {
|
||||
moving_files++
|
||||
@@ -228,7 +221,7 @@ const move_start = () => {
|
||||
const move_here = async () => {
|
||||
dispatch("loading", true)
|
||||
|
||||
let target_dir = state.base.path + "/"
|
||||
let target_dir = nav.base.path + "/"
|
||||
|
||||
try {
|
||||
let promises = []
|
||||
@@ -244,7 +237,7 @@ const move_here = async () => {
|
||||
alert("Move failed: " + err.message + " ("+err.value+")")
|
||||
} finally {
|
||||
viewing_mode()
|
||||
fs_navigator.reload()
|
||||
nav.reload()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,10 +261,10 @@ onMount(() => {
|
||||
<button on:click={navigate_back} title="Back">
|
||||
<i class="icon">arrow_back</i>
|
||||
</button>
|
||||
<button on:click={navigate_up} disabled={state.path.length <= 1} title="Up">
|
||||
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<i class="icon">north</i>
|
||||
</button>
|
||||
<button on:click={fs_navigator.reload()} title="Refresh directory listing">
|
||||
<button on:click={() => nav.reload()} title="Refresh directory listing">
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
|
||||
@@ -300,7 +293,7 @@ onMount(() => {
|
||||
</button>
|
||||
|
||||
<div class="toolbar_spacer"></div>
|
||||
{#if state.permissions.update}
|
||||
{#if $nav.permissions.update}
|
||||
<button on:click={() => dispatch("upload_picker")} title="Upload files to this directory">
|
||||
<i class="icon">cloud_upload</i>
|
||||
</button>
|
||||
@@ -335,7 +328,7 @@ onMount(() => {
|
||||
{:else if mode === "moving"}
|
||||
<div class="toolbar toolbar_edit">
|
||||
<Button click={viewing_mode} icon="close"/>
|
||||
<Button click={navigate_up} disabled={state.path.length <= 1} icon="north"/>
|
||||
<Button click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} icon="north"/>
|
||||
<div class="toolbar_spacer">
|
||||
Moving {moving_files} files and {moving_directories} directories
|
||||
</div>
|
||||
@@ -345,14 +338,10 @@ onMount(() => {
|
||||
{/if}
|
||||
|
||||
{#if creating_dir}
|
||||
<CreateDirectory
|
||||
state={state}
|
||||
on:done={() => {fs_navigator.reload(); creating_dir = false;}}
|
||||
on:loading
|
||||
/>
|
||||
<CreateDirectory nav={nav} on:done={() => nav.reload()} on:loading />
|
||||
{/if}
|
||||
|
||||
{#if state.base.path === "/me"}
|
||||
{#if $nav.base.path === "/me"}
|
||||
<div class="highlight_shaded" style="background-color: rgba(255, 255, 0, 0.05); border-radius: 0;">
|
||||
The filesystem is experimental!
|
||||
<a href="/filesystem">Please read the guide</a>
|
||||
@@ -361,12 +350,12 @@ onMount(() => {
|
||||
</div>
|
||||
|
||||
|
||||
{#if state.base.abuse_type !== undefined}
|
||||
{#if $nav.base.abuse_type !== undefined}
|
||||
<div class="highlight_red">
|
||||
This directory has received an abuse report. It cannot be
|
||||
shared.<br/>
|
||||
Type of abuse: {state.base.abuse_type}<br/>
|
||||
Report time: {formatDate(state.base.abuse_report_time, true, true, true)}
|
||||
Type of abuse: {$nav.base.abuse_type}<br/>
|
||||
Report time: {formatDate($nav.base.abuse_report_time, true, true, true)}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -374,7 +363,7 @@ onMount(() => {
|
||||
|
||||
{#if directory_view === "list"}
|
||||
<ListView
|
||||
state={state}
|
||||
nav={nav}
|
||||
show_hidden={show_hidden}
|
||||
large_icons={large_icons}
|
||||
on:node_click={node_click}
|
||||
@@ -386,7 +375,7 @@ onMount(() => {
|
||||
/>
|
||||
{:else if directory_view === "gallery"}
|
||||
<GalleryView
|
||||
state={state}
|
||||
nav={nav}
|
||||
show_hidden={show_hidden}
|
||||
large_icons={large_icons}
|
||||
on:node_click={node_click}
|
||||
@@ -398,10 +387,10 @@ onMount(() => {
|
||||
</div>
|
||||
|
||||
<FileImporter
|
||||
state={state}
|
||||
nav={nav}
|
||||
bind:this={file_importer}
|
||||
on:loading
|
||||
on:reload={() => fs_navigator.reload()}
|
||||
on:reload={() => nav.reload()}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
@@ -3,12 +3,11 @@ import { createEventDispatcher, onMount } from 'svelte'
|
||||
import ListView from './ListView.svelte'
|
||||
import GalleryView from './GalleryView.svelte'
|
||||
import Modal from '../../util/Modal.svelte';
|
||||
import Navigator from '../Navigator.svelte';
|
||||
import LoadingIndicator from '../../util/LoadingIndicator.svelte';
|
||||
import Breadcrumbs from '../Breadcrumbs.svelte'
|
||||
import { Navigator } from '../Navigator';
|
||||
|
||||
let fs_navigator
|
||||
let state
|
||||
let nav = new Navigator(false)
|
||||
let modal
|
||||
let dispatch = createEventDispatcher()
|
||||
let directory_view = ""
|
||||
@@ -20,10 +19,10 @@ export let select_multiple = false
|
||||
|
||||
export const open = path => {
|
||||
modal.show()
|
||||
fs_navigator.navigate(path, false)
|
||||
nav.navigate(path, false)
|
||||
}
|
||||
|
||||
$: selected_files = state && state.children.reduce((acc, file) => {
|
||||
$: selected_files = $nav.children.reduce((acc, file) => {
|
||||
if (file.fm_selected) {
|
||||
acc++
|
||||
}
|
||||
@@ -35,8 +34,8 @@ $: selected_files = state && state.children.reduce((acc, file) => {
|
||||
const node_click = e => {
|
||||
let index = e.detail
|
||||
|
||||
if (state.children[index].type === "dir") {
|
||||
fs_navigator.navigate(state.children[index].path, true)
|
||||
if (nav.children[index].type === "dir") {
|
||||
nav.navigate(nav.children[index].path, true)
|
||||
} else {
|
||||
select_node(index)
|
||||
}
|
||||
@@ -51,13 +50,7 @@ let node_context = e => {
|
||||
const node_select = e => {
|
||||
let index = e.detail
|
||||
mode = "selecting"
|
||||
state.children[index].fm_selected = !state.children[index].fm_selected
|
||||
}
|
||||
const navigate_up = () => {
|
||||
// Go to the path of the last parent
|
||||
if (state.path.length > 1) {
|
||||
fs_navigator.navigate(state.path[state.path.length-2].path, true)
|
||||
}
|
||||
nav.children[index].fm_selected = !nav.children[index].fm_selected
|
||||
}
|
||||
const toggle_view = () => {
|
||||
if (directory_view === "list") {
|
||||
@@ -87,17 +80,17 @@ const select_node = index => {
|
||||
|
||||
for (let i = id_low; i <= id_high; i++) {
|
||||
if (i != last_selected_node) {
|
||||
state.children[i].fm_selected = !state.children[i].fm_selected
|
||||
nav.children[i].fm_selected = !nav.children[i].fm_selected
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Deselect all other entries first
|
||||
if (!select_multiple) {
|
||||
for (let i = 0; i < state.children.length; i++) {
|
||||
state.children[i].fm_selected = false
|
||||
for (let i = 0; i < nav.children.length; i++) {
|
||||
nav.children[i].fm_selected = false
|
||||
}
|
||||
}
|
||||
state.children[index].fm_selected = !state.children[index].fm_selected
|
||||
nav.children[index].fm_selected = !nav.children[index].fm_selected
|
||||
}
|
||||
|
||||
last_selected_node = index
|
||||
@@ -105,9 +98,9 @@ const select_node = index => {
|
||||
|
||||
let done = () => {
|
||||
let selected_files = []
|
||||
for (let i = 0; i < state.children.length; i++) {
|
||||
if (state.children[i].fm_selected) {
|
||||
selected_files.push(state.children[i])
|
||||
for (let i = 0; i < nav.children.length; i++) {
|
||||
if (nav.children[i].fm_selected) {
|
||||
selected_files.push(nav.children[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,22 +123,15 @@ onMount(() => {
|
||||
|
||||
<svelte:window on:keydown={detect_shift} on:keyup={detect_shift} />
|
||||
|
||||
<Navigator
|
||||
history_enabled={false}
|
||||
bind:this={fs_navigator}
|
||||
bind:state
|
||||
on:loading={loading_evt}
|
||||
/>
|
||||
|
||||
<Modal bind:this={modal} width="900px">
|
||||
<div class="header" slot="title">
|
||||
<button class="button round" on:click={modal.hide}>
|
||||
<i class="icon">close</i>
|
||||
</button>
|
||||
<button on:click={navigate_up} disabled={state.path.length <= 1} title="Up">
|
||||
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||
<i class="icon">north</i>
|
||||
</button>
|
||||
<button on:click={fs_navigator.reload()} title="Refresh directory listing">
|
||||
<button on:click={() => nav.reload()} title="Refresh directory listing">
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
|
||||
@@ -174,20 +160,22 @@ onMount(() => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Breadcrumbs state={state} fs_navigator={fs_navigator}/>
|
||||
<Breadcrumbs nav={nav}/>
|
||||
|
||||
{#if directory_view === "list"}
|
||||
<ListView
|
||||
state={state}
|
||||
nav={nav}
|
||||
show_hidden={show_hidden}
|
||||
large_icons={large_icons}
|
||||
hide_edit
|
||||
hide_branding
|
||||
on:node_click={node_click}
|
||||
on:node_context={node_context}
|
||||
on:node_select={node_select}
|
||||
/>
|
||||
{:else if directory_view === "gallery"}
|
||||
<GalleryView
|
||||
state={state}
|
||||
nav={nav}
|
||||
show_hidden={show_hidden}
|
||||
large_icons={large_icons}
|
||||
on:node_click={node_click}
|
||||
|
@@ -3,13 +3,13 @@ import { createEventDispatcher } from "svelte"
|
||||
import { fs_encode_path, fs_node_icon, fs_node_type } from "../FilesystemUtil";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
export let show_hidden = false
|
||||
export let large_icons = false
|
||||
</script>
|
||||
|
||||
<div class="gallery">
|
||||
{#each state.children as child, index (child.path)}
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
<a class="file"
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
on:click|preventDefault={() => dispatch("node_click", index)}
|
||||
|
@@ -5,7 +5,7 @@ import { fs_encode_path, fs_node_icon } from "../FilesystemUtil";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
export let show_hidden = false
|
||||
export let large_icons = false
|
||||
export let hide_edit = false
|
||||
@@ -19,7 +19,7 @@ export let hide_branding = false
|
||||
<td>Size</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{#each state.children as child, index (child.path)}
|
||||
{#each $nav.children as child, index (child.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
on:click|preventDefault={() => dispatch("node_click", index)}
|
||||
@@ -57,7 +57,7 @@ export let hide_branding = false
|
||||
<i class="icon">palette</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if state.permissions.update && !hide_edit}
|
||||
{#if $nav.permissions.update && !hide_edit}
|
||||
<button class="action_button" on:click|preventDefault|stopPropagation={() => dispatch("node_settings", index)}>
|
||||
<i class="icon">edit</i>
|
||||
</button>
|
||||
|
@@ -3,9 +3,8 @@ import { createEventDispatcher, tick } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import DropUpload from "./DropUpload.svelte";
|
||||
import UploadProgress from "./UploadProgress.svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let fs_state
|
||||
export let nav
|
||||
|
||||
let file_input_field;
|
||||
let file_input_change = e => {
|
||||
@@ -27,7 +26,7 @@ let task_id_counter = 0
|
||||
export const upload_files = async files => {
|
||||
if (files.length === 0) {
|
||||
return
|
||||
} else if (fs_state.base.type !== "dir") {
|
||||
} else if (nav.base.type !== "dir") {
|
||||
alert("Can only upload to directory")
|
||||
return
|
||||
}
|
||||
@@ -39,14 +38,14 @@ export const upload_files = async files => {
|
||||
}
|
||||
|
||||
export const upload_file = async file => {
|
||||
if (fs_state.base.type !== "dir") {
|
||||
if (nav.base.type !== "dir") {
|
||||
alert("Can only upload to directory")
|
||||
return
|
||||
} else if (file.type === "" && file.size === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let path = fs_state.base.path + "/"
|
||||
let path = nav.base.path + "/"
|
||||
if (file.webkitRelativePath) {
|
||||
path += file.webkitRelativePath
|
||||
} else {
|
||||
@@ -101,7 +100,7 @@ const start_upload = async () => {
|
||||
|
||||
if (active_uploads === 0) {
|
||||
state = "finished"
|
||||
dispatch("uploads_finished")
|
||||
nav.reload()
|
||||
|
||||
// Empty the queue to free any references to lingering components
|
||||
upload_queue = []
|
||||
|
@@ -4,8 +4,7 @@ import { fs_encode_path, fs_node_icon, fs_path_url } from '../FilesystemUtil';
|
||||
import FileTitle from '../../file_viewer/viewers/FileTitle.svelte';
|
||||
import TextBlock from '../../file_viewer/viewers/TextBlock.svelte';
|
||||
|
||||
export let fs_navigator
|
||||
export let state
|
||||
export let nav
|
||||
let player
|
||||
let playing = false
|
||||
let media_session = false
|
||||
@@ -18,13 +17,13 @@ export const toggle_playback = () => {
|
||||
export const update = async () => {
|
||||
if (media_session) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: state.base.name,
|
||||
title: nav.base.name,
|
||||
artist: "pixeldrain",
|
||||
album: "unknown",
|
||||
});
|
||||
}
|
||||
|
||||
siblings = await fs_navigator.get_siblings()
|
||||
siblings = await nav.get_siblings()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -33,30 +32,30 @@ onMount(() => {
|
||||
navigator.mediaSession.setActionHandler('play', () => player.play());
|
||||
navigator.mediaSession.setActionHandler('pause', () => player.pause());
|
||||
navigator.mediaSession.setActionHandler('stop', () => player.stop());
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => fs_navigator.open_sibling(-1));
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => fs_navigator.open_sibling(1));
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => nav.open_sibling(-1));
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => nav.open_sibling(1));
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<FileTitle title={state.base.name}/>
|
||||
<FileTitle title={$nav.base.name}/>
|
||||
|
||||
<TextBlock width="1000px">
|
||||
<audio
|
||||
bind:this={player}
|
||||
class="player"
|
||||
src={fs_path_url(state.base.path)}
|
||||
src={fs_path_url($nav.base.path)}
|
||||
autoplay="autoplay"
|
||||
controls="controls"
|
||||
on:pause={() => playing = false }
|
||||
on:play={() => playing = true }
|
||||
on:ended={() => fs_navigator.open_sibling(1) }>
|
||||
on:ended={() => nav.open_sibling(1) }>
|
||||
<track kind="captions"/>
|
||||
</audio>
|
||||
<div style="text-align: center;">
|
||||
<button on:click={() => fs_navigator.open_sibling(-1) }><i class="icon">skip_previous</i></button>
|
||||
<button on:click={() => nav.open_sibling(-1) }><i class="icon">skip_previous</i></button>
|
||||
<button on:click={() => player.currentTime -= 10 }><i class="icon">replay_10</i></button>
|
||||
<button on:click={toggle_playback}>
|
||||
{#if playing}
|
||||
@@ -66,17 +65,17 @@ onMount(() => {
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={() => player.currentTime += 10 }><i class="icon">forward_10</i></button>
|
||||
<button on:click={() => fs_navigator.open_sibling(1) }><i class="icon">skip_next</i></button>
|
||||
<button on:click={() => nav.open_sibling(1) }><i class="icon">skip_next</i></button>
|
||||
</div>
|
||||
|
||||
<h2>Tracklist</h2>
|
||||
{#each siblings as sibling (sibling.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(sibling.path)}
|
||||
on:click|preventDefault={() => fs_navigator.navigate(sibling.path, true)}
|
||||
on:click|preventDefault={() => nav.navigate(sibling.path, true)}
|
||||
class="node"
|
||||
>
|
||||
{#if sibling.path === state.base.path}
|
||||
{#if sibling.path === $nav.base.path}
|
||||
<i class="play_arrow icon">play_arrow</i>
|
||||
{:else}
|
||||
<img src={fs_node_icon(sibling, 64, 64)} class="node_icon" alt="icon"/>
|
||||
|
@@ -5,15 +5,15 @@ import { fs_thumbnail_url } from "../FilesystemUtil";
|
||||
import TextBlock from "../../file_viewer/viewers/TextBlock.svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
</script>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<h1>{state.base.name}</h1>
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
<IconBlock icon_href={fs_thumbnail_url(state.base.path, 256, 256)}>
|
||||
Type: {state.base.file_type}<br/>
|
||||
<IconBlock icon_href={fs_thumbnail_url($nav.base.path, 256, 256)}>
|
||||
Type: {$nav.base.file_type}<br/>
|
||||
No preview is available for this file type. Download to view it locally.
|
||||
<br/>
|
||||
<button class="button_highlight" on:click={() => {dispatch("download")}}>
|
||||
@@ -22,7 +22,7 @@ export let state
|
||||
</button>
|
||||
</IconBlock>
|
||||
|
||||
{#if state.base.path === "/me/.search_index.gz"}
|
||||
{#if $nav.base.path === "/me/.search_index.gz"}
|
||||
<TextBlock>
|
||||
<p>
|
||||
Congratulations! You have found the search index. One of the
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { tick } from "svelte";
|
||||
import { onMount, tick } from "svelte";
|
||||
import Spinner from "../../util/Spinner.svelte";
|
||||
import { fs_node_type } from "../FilesystemUtil";
|
||||
import FileManager from "../filemanager/FileManager.svelte";
|
||||
@@ -13,25 +13,25 @@ import Torrent from "./Torrent.svelte";
|
||||
import Zip from "./Zip.svelte";
|
||||
import CustomBanner from "./CustomBanner.svelte";
|
||||
|
||||
export let fs_navigator
|
||||
export let nav
|
||||
export let edit_window
|
||||
|
||||
export let state
|
||||
let viewer
|
||||
let viewer_type = ""
|
||||
let last_path = ""
|
||||
|
||||
$: state_update(state.base)
|
||||
const state_update = async (base) => {
|
||||
if (base.path === last_path) {
|
||||
onMount(() => nav.subscribe(state_update))
|
||||
|
||||
const state_update = async () => {
|
||||
if (!nav.initialized || nav.base.path === last_path) {
|
||||
return
|
||||
}
|
||||
last_path = base.path
|
||||
last_path = nav.base.path
|
||||
|
||||
// Update the viewer area with the right viewer type
|
||||
viewer_type = fs_node_type(base)
|
||||
viewer_type = fs_node_type(nav.base)
|
||||
|
||||
console.debug("Previewing file", base, "viewer type", viewer_type)
|
||||
console.debug("Previewing file", nav.base, "viewer type", viewer_type)
|
||||
|
||||
// Render the viewer component and set the file type
|
||||
await tick()
|
||||
@@ -52,40 +52,34 @@ export const toggle_playback = () => {
|
||||
<Spinner></Spinner>
|
||||
</div>
|
||||
{:else if viewer_type === "dir"}
|
||||
<FileManager
|
||||
fs_navigator={fs_navigator}
|
||||
state={state}
|
||||
edit_window={edit_window}
|
||||
on:loading
|
||||
on:upload_picker
|
||||
>
|
||||
<CustomBanner path={state.path}/>
|
||||
<FileManager nav={nav} edit_window={edit_window} on:loading on:upload_picker>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</FileManager>
|
||||
{:else if viewer_type === "audio"}
|
||||
<Audio state={state} bind:this={viewer} fs_navigator={fs_navigator}>
|
||||
<CustomBanner path={state.path}/>
|
||||
<Audio nav={nav} bind:this={viewer}>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</Audio>
|
||||
{:else if viewer_type === "image"}
|
||||
<Image state={state} bind:this={viewer} on:open_sibling/>
|
||||
<Image nav={nav} bind:this={viewer}/>
|
||||
{:else if viewer_type === "video"}
|
||||
<Video state={state} bind:this={viewer} on:open_sibling/>
|
||||
<Video nav={nav} bind:this={viewer} on:open_sibling/>
|
||||
{:else if viewer_type === "pdf"}
|
||||
<Pdf state={state}/>
|
||||
<Pdf nav={nav}/>
|
||||
{:else if viewer_type === "text"}
|
||||
<Text state={state} bind:this={viewer}>
|
||||
<CustomBanner path={state.path}/>
|
||||
<Text nav={nav} bind:this={viewer}>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</Text>
|
||||
{:else if viewer_type === "torrent"}
|
||||
<Torrent state={state} bind:this={viewer} on:loading on:download>
|
||||
<CustomBanner path={state.path}/>
|
||||
<Torrent nav={nav} bind:this={viewer} on:loading on:download>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</Torrent>
|
||||
{:else if viewer_type === "zip"}
|
||||
<Zip state={state} bind:this={viewer} on:loading on:download>
|
||||
<CustomBanner path={state.path}/>
|
||||
<Zip nav={nav} bind:this={viewer} on:loading on:download>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</Zip>
|
||||
{:else}
|
||||
<File state={state} on:download>
|
||||
<CustomBanner path={state.path}/>
|
||||
<File nav={nav} on:download>
|
||||
<CustomBanner path={$nav.path}/>
|
||||
</File>
|
||||
{/if}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import { fs_path_url } from "../FilesystemUtil";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
let container
|
||||
let zoom = false
|
||||
let x, y = 0
|
||||
@@ -64,8 +64,8 @@ const mouseup = (e) => {
|
||||
class:zoom
|
||||
use:swipe_nav={!zoom}
|
||||
on:style={e => swipe_style = e.detail}
|
||||
on:prev={() => dispatch("open_sibling", -1)}
|
||||
on:next={() => dispatch("open_sibling", 1)}
|
||||
on:prev={() => nav.open_sibling(-1)}
|
||||
on:next={() => nav.open_sibling(1)}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
@@ -77,7 +77,7 @@ const mouseup = (e) => {
|
||||
class="image"
|
||||
class:zoom
|
||||
style={swipe_style}
|
||||
src={fs_path_url(state.base.path)}
|
||||
src={fs_path_url($nav.base.path)}
|
||||
alt="no description available" />
|
||||
</div>
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<script>
|
||||
import { fs_path_url } from "../FilesystemUtil";
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
</script>
|
||||
|
||||
<iframe
|
||||
class="container"
|
||||
src={"/res/misc/pdf-viewer/web/viewer.html?file="+encodeURIComponent(fs_path_url(state.base.path))}
|
||||
src={"/res/misc/pdf-viewer/web/viewer.html?file="+encodeURIComponent(fs_path_url($nav.base.path))}
|
||||
title="PDF viewer">
|
||||
</iframe>
|
||||
|
||||
|
@@ -2,26 +2,25 @@
|
||||
import { tick } from "svelte";
|
||||
import { fs_path_url } from "../FilesystemUtil";
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
let text_type = "text"
|
||||
|
||||
export const update = () => {
|
||||
const file = state.base
|
||||
console.debug("Loading text file", file.name)
|
||||
console.debug("Loading text file", nav.base.name)
|
||||
|
||||
if (file.size > 1 << 21) { // File larger than 2 MiB
|
||||
if (nav.base.size > 1 << 21) { // File larger than 2 MiB
|
||||
text_pre.innerText = "File is too large to view online.\nPlease download and view it locally."
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
file.file_type.startsWith("text/markdown") ||
|
||||
file.name.endsWith(".md") ||
|
||||
file.name.endsWith(".markdown")
|
||||
nav.base.file_type.startsWith("text/markdown") ||
|
||||
nav.base.name.endsWith(".md") ||
|
||||
nav.base.name.endsWith(".markdown")
|
||||
) {
|
||||
markdown(file)
|
||||
markdown(nav.base)
|
||||
} else {
|
||||
text(file)
|
||||
text(nav.base)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import CopyButton from "../../layout/CopyButton.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
|
||||
let status = "loading"
|
||||
|
||||
@@ -18,7 +18,7 @@ export const update = async () => {
|
||||
dispatch("loading", true)
|
||||
|
||||
try {
|
||||
let resp = await fetch(fs_path_url(state.base.path)+"?torrent_info")
|
||||
let resp = await fetch(fs_path_url(nav.base.path)+"?torrent_info")
|
||||
|
||||
if (resp.status >= 400) {
|
||||
let json = await resp.json()
|
||||
@@ -63,9 +63,9 @@ let magnet = ""
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<h1>{state.base.name}</h1>
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
<IconBlock icon_href={fs_node_icon(state.base, 256, 256)}>
|
||||
<IconBlock icon_href={fs_node_icon($nav.base, 256, 256)}>
|
||||
{#if status === "finished"}
|
||||
Created by: {torrent.created_by}<br/>
|
||||
Comment: {torrent.comment}<br/>
|
||||
|
@@ -3,7 +3,7 @@ import { onMount, createEventDispatcher, tick } from "svelte";
|
||||
import { fs_path_url } from "../FilesystemUtil";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
|
||||
// Used to detect when the file path changes
|
||||
let last_path = ""
|
||||
@@ -17,20 +17,20 @@ let loop = false
|
||||
export const update = async () => {
|
||||
if (media_session) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: state.base.name,
|
||||
title: nav.base.name,
|
||||
artist: "pixeldrain",
|
||||
album: "unknown",
|
||||
});
|
||||
console.debug("Updating media session")
|
||||
}
|
||||
|
||||
loop = state.base.name.includes(".loop.")
|
||||
loop = nav.base.name.includes(".loop.")
|
||||
|
||||
// When the component receives a new ID the video track does not
|
||||
// automatically start playing the new video. So we use this little hack to
|
||||
// make sure that the video is unloaded and loaded when the ID changes
|
||||
if (state.base.path != last_path) {
|
||||
last_path = state.base.path
|
||||
if (nav.base.path != last_path) {
|
||||
last_path = nav.base.path
|
||||
loaded = false
|
||||
await tick()
|
||||
loaded = true
|
||||
@@ -75,9 +75,9 @@ const fullscreen = () => {
|
||||
|
||||
<div class="container">
|
||||
{#if
|
||||
state.base.file_type === "video/x-matroska" ||
|
||||
state.base.file_type === "video/quicktime" ||
|
||||
state.base.file_type === "video/x-ms-asf"
|
||||
$nav.base.file_type === "video/x-matroska" ||
|
||||
$nav.base.file_type === "video/quicktime" ||
|
||||
$nav.base.file_type === "video/x-ms-asf"
|
||||
}
|
||||
<div class="compatibility_warning">
|
||||
This video file type is not compatible with every web
|
||||
@@ -100,7 +100,7 @@ const fullscreen = () => {
|
||||
on:play={() => playing = true }
|
||||
on:ended={() => dispatch("open_sibling", 1)}
|
||||
>
|
||||
<source src={fs_path_url(state.base.path)} type={state.base.file_type} />
|
||||
<source src={fs_path_url($nav.base.path)} type={$nav.base.file_type} />
|
||||
</video>
|
||||
{/if}
|
||||
</div>
|
||||
|
@@ -8,7 +8,7 @@ import { fs_node_icon, fs_path_url } from "../FilesystemUtil";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
export let nav
|
||||
|
||||
let status = "loading"
|
||||
|
||||
@@ -23,14 +23,14 @@ let archive_type = ""
|
||||
export const update = async () => {
|
||||
dispatch("loading", true)
|
||||
|
||||
if (state.base.file_type === "application/zip") {
|
||||
if (nav.base.file_type === "application/zip") {
|
||||
archive_type = "zip"
|
||||
} else if (state.base.file_type === "application/x-7z-compressed") {
|
||||
} else if (nav.base.file_type === "application/x-7z-compressed") {
|
||||
archive_type = "7z"
|
||||
}
|
||||
|
||||
try {
|
||||
let resp = await fetch(fs_path_url(state.base.path)+"?zip_info")
|
||||
let resp = await fetch(fs_path_url(nav.base.path)+"?zip_info")
|
||||
|
||||
if (resp.status >= 400) {
|
||||
status = "parse_failed"
|
||||
@@ -43,11 +43,11 @@ export const update = async () => {
|
||||
// downloaded. If so then we set the download URL for each file
|
||||
if (zip.properties && zip.properties.includes("read_individual_files")) {
|
||||
// Set the download URL for each file in the zip
|
||||
recursive_set_url(fs_path_url(state.base.path)+"?zip_file=", zip)
|
||||
recursive_set_url(fs_path_url(nav.base.path)+"?zip_file=", zip)
|
||||
}
|
||||
|
||||
uncomp_size = recursive_size(zip)
|
||||
comp_ratio = (uncomp_size / state.base.file_size)
|
||||
comp_ratio = (uncomp_size / nav.base.file_size)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
@@ -85,18 +85,18 @@ const recursive_size = (file) => {
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<h1>{state.base.name}</h1>
|
||||
<h1>{$nav.base.name}</h1>
|
||||
|
||||
<IconBlock icon_href={fs_node_icon(state.base, 256, 256)}>
|
||||
<IconBlock icon_href={fs_node_icon($nav.base, 256, 256)}>
|
||||
{#if archive_type === "7z"}
|
||||
This is a 7-zip archive. You will need
|
||||
<a href="https://www.7-zip.org/">7-zip</a> or compatible software to
|
||||
extract it<br/>
|
||||
{/if}
|
||||
|
||||
Compressed size: {formatDataVolume(state.base.file_size, 3)}<br/>
|
||||
Compressed size: {formatDataVolume($nav.base.file_size, 3)}<br/>
|
||||
Uncompressed size: {formatDataVolume(zip.size, 3)} (Ratio: {comp_ratio.toFixed(2)}x)<br/>
|
||||
Uploaded on: {formatDate(state.base.created, true, true, true)}
|
||||
Uploaded on: {formatDate($nav.base.created, true, true, true)}
|
||||
<br/>
|
||||
<button class="button_highlight" on:click={() => {dispatch("download")}}>
|
||||
<i class="icon">download</i>
|
||||
|
@@ -1,22 +1,16 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import Navigator from "../../filesystem/Navigator.svelte";
|
||||
import { Navigator } from "../../filesystem/Navigator"
|
||||
import { fs_encode_path, fs_node_icon } from "../../filesystem/FilesystemUtil";
|
||||
|
||||
let nav;
|
||||
let state = {
|
||||
children: [],
|
||||
};
|
||||
|
||||
const nav = new Navigator(false)
|
||||
onMount(() => {
|
||||
nav.navigate("/me", false)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Navigator bind:this={nav} bind:state={state} history_enabled={false}/>
|
||||
|
||||
<div class="directory">
|
||||
{#each state.children as child, index (child.path)}
|
||||
{#each $nav.children as child (child.path)}
|
||||
<a
|
||||
href={"/d"+fs_encode_path(child.path)}
|
||||
class="node"
|
||||
|
Reference in New Issue
Block a user