Change filesystem navigator into a class with a svelte store implementation

This commit is contained in:
2024-08-09 13:02:07 +02:00
parent c481a89051
commit 69744e41a6
28 changed files with 534 additions and 530 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}

View 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 })
})
}

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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

View File

@@ -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}

View File

@@ -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} />

View File

@@ -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)

View File

@@ -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>

View File

@@ -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}

View File

@@ -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)}

View File

@@ -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>

View File

@@ -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 = []

View File

@@ -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"/>

View File

@@ -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

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)
}
}

View File

@@ -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/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"