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