Files
fnx_web/svelte/src/filesystem/filemanager/FilePicker.svelte

226 lines
5.5 KiB
Svelte
Raw Normal View History

<script lang="ts">
2025-10-14 00:03:48 +02:00
import { onMount } from "svelte"
import ListView from "./ListView.svelte"
import GalleryView from "./GalleryView.svelte"
import CompactView from "./CompactView.svelte"
import Modal from "util/Modal.svelte";
import Breadcrumbs from "filesystem/Breadcrumbs.svelte"
import { FSNavigator } from "filesystem/FSNavigator";
import type { FSNode } from "lib/FilesystemAPI.svelte";
import { FileAction, type FileActionHandler } from "./FileManagerLib";
2025-10-13 16:05:50 +02:00
let nav = $state(new FSNavigator(false))
let modal: Modal = $state()
let directory_view = $state("")
let large_icons = $state(false)
let show_hidden = $state(false)
2025-10-14 00:03:48 +02:00
let {
callback,
select_multiple = false
}: {
callback: (files: FSNode[]) => void
select_multiple?: boolean
2025-10-13 16:05:50 +02:00
} = $props();
export const open = (path: string) => {
modal.show()
nav.navigate(path, false)
}
2025-10-13 16:05:50 +02:00
let selected_files = $derived($nav.children.reduce((acc, file) => {
if (file.fm_selected) {
acc++
}
return acc
2025-10-13 16:05:50 +02:00
}, 0))
// Navigation functions
const file_event: FileActionHandler = (action: FileAction, index: number, orig: Event) => {
switch (action) {
case FileAction.Click:
orig.preventDefault()
if (nav.children[index].type === "dir") {
nav.navigate(nav.children[index].path, true)
} else {
select_node(index)
}
break
case FileAction.Context:
// If this is a touch event we will select the item
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) {
orig.preventDefault()
select_node(index)
}
break
case FileAction.Select:
orig.preventDefault()
nav.children[index].fm_selected = !nav.children[index].fm_selected
break
}
}
2025-01-28 12:16:37 +01:00
const toggle_view = () => {
if (directory_view === "list") {
directory_view = "gallery"
2025-01-28 12:16:37 +01:00
} else if (directory_view === "gallery") {
directory_view = "compact"
} else {
directory_view = "list"
}
localStorage.setItem("directory_view", directory_view)
}
// We need to detect if shift is pressed so we can select multiple items
let shift_pressed = false
let last_selected_node = -1
const detect_shift = (e: KeyboardEvent) => {
if (e.key === "Shift") {
shift_pressed = e.type === "keydown"
}
}
const select_node = (index: number) => {
if (select_multiple && shift_pressed) {
// If shift is pressed we do a range select. We select all files between
// the last selected file and the file that is being selected now
let id_low = Math.min(last_selected_node, index)
let id_high = Math.max(last_selected_node, index)
for (let i = id_low; i <= id_high; i++) {
if (i != last_selected_node) {
nav.children[i].fm_selected = !nav.children[i].fm_selected
}
}
} else {
// Deselect all other entries first
if (!select_multiple) {
for (let i = 0; i < nav.children.length; i++) {
nav.children[i].fm_selected = false
}
}
nav.children[index].fm_selected = !nav.children[index].fm_selected
}
last_selected_node = index
}
let done = () => {
let selected_files: FSNode[] = []
for (let i = 0; i < nav.children.length; i++) {
if (nav.children[i].fm_selected) {
selected_files.push(nav.children[i])
}
}
if (selected_files.length > 0) {
2025-10-14 00:03:48 +02:00
callback(selected_files)
}
modal.hide()
}
onMount(() => {
if(typeof Storage !== "undefined") {
directory_view = localStorage.getItem("directory_view")
large_icons = localStorage.getItem("large_icons") === "true"
}
if (directory_view === "" || directory_view === null) {
directory_view = "list"
}
})
</script>
2025-10-13 16:05:50 +02:00
<svelte:window onkeydown={detect_shift} onkeyup={detect_shift} />
<Modal bind:this={modal} width="900px">
{#snippet header()}
2025-10-13 16:05:50 +02:00
<div class="header" >
<button class="button round" onclick={modal.hide}>
<i class="icon">close</i>
</button>
<button onclick={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
<i class="icon">north</i>
</button>
<button onclick={() => nav.reload()} title="Refresh directory listing">
<i class="icon">refresh</i>
</button>
<div class="title">
Selected {selected_files} files
</div>
<button onclick={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
{#if show_hidden}
<i class="icon">visibility_off</i>
{:else}
<i class="icon">visibility</i>
{/if}
</button>
<button onclick={() => toggle_view()} title="Switch between gallery, list and compact view">
<i class="icon" class:button_highlight={directory_view === "list"}>list</i>
<i class="icon" class:button_highlight={directory_view === "gallery"}>collections</i>
<i class="icon" class:button_highlight={directory_view === "compact"}>view_compact</i>
</button>
<button class="button button_highlight round" onclick={done}>
<i class="icon">done</i> Pick
</button>
</div>
2025-10-13 16:05:50 +02:00
{/snippet}
2024-02-16 14:50:34 +01:00
<Breadcrumbs nav={nav}/>
2024-02-16 14:50:34 +01:00
{#if directory_view === "list"}
<ListView
nav={nav}
file_event={file_event}
show_hidden={show_hidden}
large_icons={large_icons}
hide_edit
hide_branding
/>
{:else if directory_view === "gallery"}
<GalleryView
nav={nav}
file_event={file_event}
show_hidden={show_hidden}
large_icons={large_icons}
/>
2025-01-28 12:16:37 +01:00
{:else if directory_view === "compact"}
<CompactView
nav={nav}
file_event={file_event}
2025-01-28 12:16:37 +01:00
show_hidden={show_hidden}
large_icons={large_icons}
hide_edit
2025-01-28 12:16:37 +01:00
/>
{/if}
</Modal>
<style>
.header {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 1em;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.title {
flex-grow: 1;
flex-shrink: 1;
text-align: center;
font-size: 1.2em;
}
2025-01-28 12:16:37 +01:00
.icon.button_highlight {
border-radius: 4px;
}
</style>