2025-03-28 14:16:20 +01:00
|
|
|
<script lang="ts">
|
2025-03-27 15:38:59 +01:00
|
|
|
import { onMount } from "svelte";
|
|
|
|
|
import EditWindow from "./edit_window/EditWindow.svelte";
|
|
|
|
|
import Toolbar from "./Toolbar.svelte";
|
|
|
|
|
import Breadcrumbs from "./Breadcrumbs.svelte";
|
|
|
|
|
import DetailsWindow from "./DetailsWindow.svelte";
|
|
|
|
|
import FilePreview from "./viewers/FilePreview.svelte";
|
|
|
|
|
import FSUploadWidget from "./upload_widget/FSUploadWidget.svelte";
|
2025-10-14 00:03:48 +02:00
|
|
|
import { type FSPath } from "lib/FilesystemAPI.svelte";
|
2026-01-29 23:41:30 +01:00
|
|
|
import { global_navigator } from "./FSNavigator"
|
2025-03-28 14:16:20 +01:00
|
|
|
import { css_from_path } from "filesystem/edit_window/Branding";
|
2025-03-27 15:38:59 +01:00
|
|
|
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";
|
2025-10-09 15:48:23 +02:00
|
|
|
import { current_page_store } from "wrap/RouterStore";
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2025-10-13 16:05:50 +02:00
|
|
|
let file_preview: FilePreview = $state()
|
|
|
|
|
let toolbar: Toolbar = $state()
|
|
|
|
|
let upload_widget: FSUploadWidget = $state()
|
|
|
|
|
let details_visible = $state(false)
|
|
|
|
|
let edit_window: EditWindow = $state()
|
|
|
|
|
let edit_visible = $state(false)
|
2025-10-14 00:03:48 +02:00
|
|
|
let details_window: DetailsWindow = $state()
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2026-01-29 23:41:30 +01:00
|
|
|
const nav = global_navigator
|
2023-05-17 15:34:56 +02:00
|
|
|
|
2024-08-09 13:02:07 +02:00
|
|
|
onMount(() => {
|
2025-10-09 15:48:23 +02:00
|
|
|
if ((window as any).intial_node !== undefined) {
|
|
|
|
|
console.debug("Loading initial node")
|
|
|
|
|
nav.open_node((window as any).initial_node as FSPath, false)
|
|
|
|
|
} else {
|
|
|
|
|
console.debug("No initial node, fetching path", window.location.pathname)
|
2026-01-27 15:33:25 +01:00
|
|
|
nav.navigate(decodeURIComponent(window.location.pathname).replace(/^\/d/, ""), false)
|
2025-10-09 15:48:23 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-27 15:33:25 +01:00
|
|
|
// There is a global natigation handler which captures link clicks and loads
|
|
|
|
|
// the right svelte components. When a filesystem link is clicked this store
|
|
|
|
|
// is updated. Catch it and use the filesystem navigator to navigate to the
|
|
|
|
|
// right file
|
2025-10-09 15:48:23 +02:00
|
|
|
const page_sub = current_page_store.subscribe(() => {
|
2026-01-27 15:33:25 +01:00
|
|
|
console.debug("Caught page transition to", window.location.pathname, "calling navigator")
|
|
|
|
|
nav.navigate(decodeURIComponent(window.location.pathname).replace(/^\/d/, ""), false)
|
2025-10-09 15:48:23 +02:00
|
|
|
})
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2024-08-09 13:02:07 +02:00
|
|
|
// Subscribe to navigation updates. This function returns a deconstructor
|
|
|
|
|
// which we can conveniently return from our mount function as well
|
2025-10-09 15:48:23 +02:00
|
|
|
const nav_sub = nav.subscribe(nav => {
|
2024-08-09 13:02:07 +02:00
|
|
|
if (!nav.initialized) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 20:00:43 +01:00
|
|
|
// Custom CSS rules for the whole viewer. The MainMenu applies its
|
|
|
|
|
// styles to the <html> element. So we apply to the <body> element, our
|
|
|
|
|
// styles take precedence since they're lower level, and we can clean it
|
|
|
|
|
// up afterwards without overwriting global style
|
|
|
|
|
document.body.style = css_from_path(nav.path)
|
2024-08-09 13:02:07 +02:00
|
|
|
})
|
2025-10-09 15:48:23 +02:00
|
|
|
return () => {
|
|
|
|
|
page_sub()
|
|
|
|
|
nav_sub()
|
2026-02-12 20:00:43 +01:00
|
|
|
document.body.style = ""
|
2025-10-09 15:48:23 +02:00
|
|
|
}
|
2024-08-09 13:02:07 +02:00
|
|
|
})
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2025-03-28 14:16:20 +01:00
|
|
|
const keydown = (e: KeyboardEvent) => {
|
2020-12-01 23:01:21 +01:00
|
|
|
if (e.ctrlKey || e.altKey || e.metaKey) {
|
|
|
|
|
return // prevent custom shortcuts from interfering with system shortcuts
|
2025-03-28 14:16:20 +01:00
|
|
|
} else if ((document.activeElement as any).type !== undefined && (document.activeElement as any).type === "text") {
|
2021-03-04 15:42:46 +01:00
|
|
|
return // Prevent shortcuts from interfering with input fields
|
|
|
|
|
}
|
2020-12-01 23:01:21 +01:00
|
|
|
|
2024-03-12 17:53:53 +01:00
|
|
|
let action_performed = true
|
2020-11-11 00:00:54 +01:00
|
|
|
switch (e.key) {
|
2023-11-16 12:17:36 +01:00
|
|
|
case "c":
|
|
|
|
|
toolbar.copy_link()
|
|
|
|
|
break;
|
2020-11-26 11:13:27 +01:00
|
|
|
case "i":
|
2023-05-17 15:34:56 +02:00
|
|
|
details_visible = !details_visible
|
|
|
|
|
break;
|
|
|
|
|
case "e":
|
|
|
|
|
if (edit_visible) {
|
|
|
|
|
edit_visible = false
|
|
|
|
|
} else {
|
2024-08-09 13:02:07 +02:00
|
|
|
edit_window.edit(nav.base, true, "file")
|
2023-05-17 15:34:56 +02:00
|
|
|
}
|
2020-12-01 23:01:21 +01:00
|
|
|
break;
|
2020-11-26 11:13:27 +01:00
|
|
|
case "s":
|
2025-10-14 00:03:48 +02:00
|
|
|
nav.base.download()
|
2020-12-01 23:01:21 +01:00
|
|
|
break;
|
|
|
|
|
case "r":
|
2024-08-09 13:02:07 +02:00
|
|
|
nav.shuffle = !nav.shuffle
|
2020-12-01 23:01:21 +01:00
|
|
|
break;
|
2023-05-17 15:34:56 +02:00
|
|
|
case "a":
|
|
|
|
|
case "ArrowLeft":
|
2024-08-09 13:02:07 +02:00
|
|
|
nav.open_sibling(-1)
|
2020-12-01 23:01:21 +01:00
|
|
|
break;
|
2023-05-17 15:34:56 +02:00
|
|
|
case "d":
|
|
|
|
|
case "ArrowRight":
|
2024-08-09 13:02:07 +02:00
|
|
|
nav.open_sibling(1)
|
2020-12-01 23:01:21 +01:00
|
|
|
break;
|
2024-04-11 20:32:55 +02:00
|
|
|
case " ": // Spacebar pauses / unpauses video and audio playback
|
|
|
|
|
if (file_preview) {
|
2025-01-27 21:05:18 +01:00
|
|
|
if (file_preview.toggle_playback()) {
|
2025-02-03 16:29:26 +01:00
|
|
|
e.preventDefault()
|
|
|
|
|
e.stopPropagation()
|
2025-01-27 21:05:18 +01:00
|
|
|
}
|
2024-04-11 20:32:55 +02:00
|
|
|
}
|
|
|
|
|
break
|
2025-02-03 15:38:07 +01:00
|
|
|
case "m": // M mutes / unmutes audio
|
|
|
|
|
if (file_preview) {
|
|
|
|
|
file_preview.toggle_mute()
|
|
|
|
|
}
|
|
|
|
|
break
|
2024-08-14 19:58:25 +02:00
|
|
|
case "h":
|
|
|
|
|
file_preview.seek(-20)
|
|
|
|
|
break
|
|
|
|
|
case "j":
|
|
|
|
|
file_preview.seek(-5)
|
|
|
|
|
break
|
|
|
|
|
case "k":
|
|
|
|
|
file_preview.seek(5)
|
|
|
|
|
break
|
|
|
|
|
case "l":
|
|
|
|
|
file_preview.seek(20)
|
|
|
|
|
break
|
|
|
|
|
case ",":
|
|
|
|
|
file_preview.seek(-0.04) // Roughly a single frame.. assuming 25fps
|
|
|
|
|
break
|
|
|
|
|
case ".":
|
|
|
|
|
file_preview.seek(0.04)
|
|
|
|
|
break
|
2024-03-12 17:53:53 +01:00
|
|
|
default:
|
|
|
|
|
action_performed = false
|
2020-11-11 00:00:54 +01:00
|
|
|
}
|
2023-05-25 17:06:17 +02:00
|
|
|
|
2024-03-12 17:53:53 +01:00
|
|
|
if (action_performed) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
}
|
2020-11-11 00:00:54 +01:00
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
2025-10-13 16:05:50 +02:00
|
|
|
<svelte:window onkeydown={keydown} />
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<div class="filesystem">
|
|
|
|
|
<Breadcrumbs nav={nav}/>
|
2020-11-11 00:00:54 +01:00
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<div class="file_preview">
|
|
|
|
|
<FilePreview
|
|
|
|
|
bind:this={file_preview}
|
2024-08-09 13:02:07 +02:00
|
|
|
nav={nav}
|
2025-10-09 15:48:23 +02:00
|
|
|
upload_widget={upload_widget}
|
2023-11-15 15:50:54 +01:00
|
|
|
edit_window={edit_window}
|
2025-10-14 00:03:48 +02:00
|
|
|
details_window={details_window}
|
2023-11-15 15:50:54 +01:00
|
|
|
/>
|
2020-11-11 00:00:54 +01:00
|
|
|
</div>
|
|
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<Toolbar
|
|
|
|
|
bind:this={toolbar}
|
|
|
|
|
nav={nav}
|
|
|
|
|
bind:details_visible={details_visible}
|
|
|
|
|
edit_window={edit_window}
|
|
|
|
|
bind:edit_visible={edit_visible}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-08-09 13:47:10 +02:00
|
|
|
|
2025-10-14 00:03:48 +02:00
|
|
|
<DetailsWindow nav={nav} bind:this={details_window} bind:visible={details_visible} />
|
2024-08-09 13:47:10 +02:00
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<EditWindow nav={nav} bind:this={edit_window} bind:visible={edit_visible} />
|
2024-08-09 13:47:10 +02:00
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<!-- This one is included at the highest level so uploads can keep running
|
|
|
|
|
even when the user navigates to a different directory -->
|
|
|
|
|
<FSUploadWidget nav={nav} bind:this={upload_widget} />
|
2025-03-21 01:11:03 +01:00
|
|
|
|
2025-10-09 15:48:23 +02:00
|
|
|
<AffiliatePrompt/>
|
2020-11-11 00:00:54 +01:00
|
|
|
|
|
|
|
|
<style>
|
2024-02-15 18:52:46 +01:00
|
|
|
:global(*) {
|
2024-11-14 16:14:58 +01:00
|
|
|
transition: background-color 0.2s,
|
|
|
|
|
border 0.2s,
|
|
|
|
|
border-top 0.2s,
|
|
|
|
|
border-right 0.2s,
|
|
|
|
|
border-bottom 0.2s,
|
|
|
|
|
border-left 0.2s,
|
|
|
|
|
color 0.2s;
|
2024-02-15 18:52:46 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-11 00:00:54 +01:00
|
|
|
/* Viewer container */
|
2025-10-09 15:48:23 +02:00
|
|
|
.filesystem {
|
2023-11-15 15:50:54 +01:00
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2025-10-09 15:48:23 +02:00
|
|
|
height: 100vh;
|
|
|
|
|
width: 100%;
|
2020-11-11 00:00:54 +01:00
|
|
|
}
|
2023-05-25 17:06:17 +02:00
|
|
|
|
|
|
|
|
.file_preview {
|
2025-10-09 15:48:23 +02:00
|
|
|
flex: 1 1 auto;
|
2023-11-15 15:50:54 +01:00
|
|
|
overflow: auto;
|
2023-05-25 17:06:17 +02:00
|
|
|
}
|
2020-11-11 00:00:54 +01:00
|
|
|
</style>
|