Update to svelte 5

This commit is contained in:
2025-10-13 16:05:50 +02:00
parent f936e4c0f2
commit 6d89c5ddd9
129 changed files with 2443 additions and 2180 deletions

View File

@@ -6,18 +6,25 @@ import { FileAction } from "./FileManagerLib";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
export let show_hidden = false
export let large_icons = false
export let hide_edit = false
let {
nav,
show_hidden = false,
large_icons = false,
hide_edit = false
}: {
nav: FSNavigator;
show_hidden?: boolean;
large_icons?: boolean;
hide_edit?: boolean;
} = $props();
</script>
<div class="directory">
{#each $nav.children as child, index (child.path)}
<a
href={"/d"+fs_encode_path(child.path)}
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
class="node"
class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden}
@@ -26,20 +33,15 @@ export let hide_edit = false
<div class="node_name">
{child.name}
</div>
{#if node_is_shared(child)}
<a
href="/d/{child.id}"
on:click={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
class="button flat action_button"
>
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
</a>
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
{/if}
{#if !hide_edit}
<button
class="action_button flat"
on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
>
<i class="icon">menu</i>
</button>

View File

@@ -1,15 +1,16 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { onMount } from "svelte";
import { fs_mkdir } from "lib/FilesystemAPI";
import Button from "layout/Button.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
let { nav }: { nav: FSNavigator } = $props();
let name_input: HTMLInputElement;
let new_dir_name = ""
let error_msg = ""
let name_input: HTMLInputElement = $state();
let new_dir_name = $state("")
let error_msg = $state("")
let create_dir = async () => {
let form = new FormData()
form.append("type", "dir")
@@ -42,7 +43,7 @@ onMount(() => {
</div>
{/if}
<form id="create_dir_form" class="create_dir" on:submit|preventDefault={create_dir}>
<form id="create_dir_form" class="create_dir" onsubmit={preventDefault(create_dir)}>
<img src="/res/img/mime/folder.png" class="icon" alt="icon"/>
<input class="dirname" type="text" bind:this={name_input} bind:value={new_dir_name} />
<Button form="create_dir_form" type="submit" icon="create_new_folder" label="Create"/>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "lib/FilesystemAPI"
import { onMount } from "svelte"
import CreateDirectory from "./CreateDirectory.svelte"
@@ -16,19 +17,29 @@ import { FileAction, type FileEvent } from "./FileManagerLib";
import FileMenu from "./FileMenu.svelte";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
export let upload_widget: FsUploadWidget
export let edit_window: EditWindow
export let directory_view = ""
let large_icons = false
let {
nav = $bindable(),
upload_widget,
edit_window = $bindable(),
directory_view = $bindable(""),
children
}: {
nav: FSNavigator;
upload_widget: FsUploadWidget;
edit_window: EditWindow;
directory_view?: string;
children?: import('svelte').Snippet;
} = $props();
let large_icons = $state(false)
let uploader: FsUploadWidget
let mode = "viewing"
let creating_dir = false
let show_hidden = false
let file_menu: FileMenu
let mode = $state("viewing")
let creating_dir = $state(false)
let show_hidden = $state(false)
let file_menu: FileMenu = $state()
export const upload = (files: File[]) => {
return uploader.upload(files)
return uploader.upload_files(files)
}
// Navigation functions
@@ -228,10 +239,6 @@ const select_node = (index: number) => {
last_selected_node = index
}
// When the directory is reloaded we want to keep our selection, so this
// function watches the children array for changes and updates the selection
// when it changes
$: update($nav.children)
const update = (children: FSNode[]) => {
creating_dir = false
@@ -245,8 +252,8 @@ const update = (children: FSNode[]) => {
}
}
let moving_files = 0
let moving_directories = 0
let moving_files = $state(0)
let moving_directories = $state(0)
const move_start = () => {
moving_files = 0
moving_directories = 0
@@ -296,9 +303,15 @@ onMount(() => {
directory_view = "list"
}
})
// When the directory is reloaded we want to keep our selection, so this
// function watches the children array for changes and updates the selection
// when it changes
run(() => {
update($nav.children)
});
</script>
<svelte:window on:keydown={keypress} on:keyup={keypress} />
<svelte:window onkeydown={keypress} onkeyup={keypress} />
<div
class="container"
@@ -311,25 +324,25 @@ onMount(() => {
{#if mode === "viewing"}
<div class="toolbar">
<div class="toolbar_left">
<button on:click={navigate_back} title="Back">
<button onclick={navigate_back} title="Back">
<i class="icon">arrow_back</i>
</button>
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
<button onclick={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
<i class="icon">north</i>
</button>
<button on:click={() => nav.reload()} title="Refresh directory listing">
<button onclick={() => nav.reload()} title="Refresh directory listing">
<i class="icon">refresh</i>
</button>
</div>
<div class="toolbar_middle">
<button on:click={() => toggle_view()} title="Switch between gallery, list and compact view">
<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_large_icons" on:click={() => toggle_large_icons()} title="Switch between large and small icons">
<button class="button_large_icons" onclick={() => toggle_large_icons()} title="Switch between large and small icons">
{#if large_icons}
<i class="icon">zoom_out</i>
{:else}
@@ -337,7 +350,7 @@ onMount(() => {
{/if}
</button>
<button on:click={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
<button onclick={() => {show_hidden = !show_hidden}} title="Toggle hidden files">
{#if show_hidden}
<i class="icon">visibility_off</i>
{:else}
@@ -348,13 +361,13 @@ onMount(() => {
<div class="toolbar_right">
{#if $nav.permissions.write}
<button on:click={() => upload_widget.pick_files()} title="Upload files to this directory">
<button onclick={() => upload_widget.pick_files()} title="Upload files to this directory">
<i class="icon">cloud_upload</i>
</button>
<Button click={() => {creating_dir = !creating_dir}} highlight={creating_dir} icon="create_new_folder" title="Make folder"/>
<button on:click={selecting_mode} title="Select and delete files">
<button onclick={selecting_mode} title="Select and delete files">
<i class="icon">select_all</i>
</button>
{/if}
@@ -368,7 +381,7 @@ onMount(() => {
<Button click={viewing_mode} icon="close"/>
<div class="toolbar_spacer">Selecting files</div>
<Button click={move_start} icon="drive_file_move" label="Move"/>
<button on:click={delete_selected} class="button_red">
<button onclick={delete_selected} class="button_red">
<i class="icon">delete</i>
Delete
</button>
@@ -407,7 +420,7 @@ onMount(() => {
</div>
{/if}
<slot></slot>
{@render children?.()}
{#if directory_view === "list"}
<ListView nav={nav} show_hidden={show_hidden} large_icons={large_icons} on:file={file_event} />
@@ -418,7 +431,7 @@ onMount(() => {
{/if}
</div>
<FileMenu bind:this={file_menu} bind:nav bind:edit_window />
<FileMenu bind:this={file_menu} nav={nav} edit_window={edit_window} />
<style>
.container {

View File

@@ -7,10 +7,16 @@ import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bo
import { fs_download, type FSNode } from "lib/FilesystemAPI";
import { tick } from "svelte";
export let nav: FSNavigator
export let edit_window: EditWindow
let dialog: Dialog
let node: FSNode = null
let {
nav,
edit_window
}: {
nav: FSNavigator;
edit_window: EditWindow;
} = $props();
let dialog: Dialog = $state()
let node: FSNode = $state(null)
export const open = async (n: FSNode, target: EventTarget) => {
node = n

View File

@@ -9,25 +9,28 @@ import { FSNavigator } from "filesystem/FSNavigator";
import type { FSNode } from "lib/FilesystemAPI";
import { FileAction, type FileEvent } from "./FileManagerLib";
let nav = new FSNavigator(false)
let modal: Modal
let nav = $state(new FSNavigator(false))
let modal: Modal = $state()
let dispatch = createEventDispatcher()
let directory_view = ""
let large_icons = false
let show_hidden = false
export let select_multiple = false
let directory_view = $state("")
let large_icons = $state(false)
let show_hidden = $state(false)
let { select_multiple = false }: {
select_multiple?: boolean;
} = $props();
export const open = (path: string) => {
modal.show()
nav.navigate(path, false)
}
$: selected_files = $nav.children.reduce((acc, file) => {
let selected_files = $derived($nav.children.reduce((acc, file) => {
if (file.fm_selected) {
acc++
}
return acc
}, 0)
}, 0))
// Navigation functions
@@ -128,42 +131,44 @@ onMount(() => {
})
</script>
<svelte:window on:keydown={detect_shift} on:keyup={detect_shift} />
<svelte:window onkeydown={detect_shift} onkeyup={detect_shift} />
<Modal bind:this={modal} width="900px">
<div class="header" slot="title">
<button class="button round" on:click={modal.hide}>
<i class="icon">close</i>
</button>
<button on:click={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
<i class="icon">north</i>
</button>
<button on:click={() => nav.reload()} title="Refresh directory listing">
<i class="icon">refresh</i>
</button>
{#snippet title()}
<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 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>
<button on:click={() => {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 on:click={() => 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" on:click={done}>
<i class="icon">done</i> Pick
</button>
</div>
{/snippet}
<Breadcrumbs nav={nav}/>

View File

@@ -5,17 +5,23 @@ import type { FSNavigator } from "filesystem/FSNavigator";
import { FileAction } from "./FileManagerLib";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
export let show_hidden = false
export let large_icons = false
let {
nav,
show_hidden = false,
large_icons = false
}: {
nav: FSNavigator;
show_hidden?: boolean;
large_icons?: boolean;
} = $props();
</script>
<div class="gallery">
{#each $nav.children as child, index (child.path)}
<a class="file"
href={"/d"+fs_encode_path(child.path)}
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
class:selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden}
class:large_icons

View File

@@ -8,68 +8,81 @@ import { FileAction } from "./FileManagerLib";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
export let show_hidden = false
export let large_icons = false
export let hide_edit = false
export let hide_branding = false
let {
nav,
show_hidden = false,
large_icons = false,
hide_edit = false,
hide_branding = false
}: {
nav: FSNavigator;
show_hidden?: boolean;
large_icons?: boolean;
hide_edit?: boolean;
hide_branding?: boolean;
} = $props();
</script>
<div class="directory">
<tr>
<td></td>
<td><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="name">Name</SortButton></td>
<td class="hide_small"><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</SortButton></td>
<td></td>
</tr>
{#each $nav.children as child, index (child.path)}
<a
href={"/d"+fs_encode_path(child.path)}
on:click={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
on:contextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
class="node"
class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden}
>
<td>
<img src={fs_node_icon(child, 64, 64)} class="node_icon" class:large_icons alt="icon"/>
</td>
<td class="node_name">
{child.name}
</td>
<td class="node_size hide_small">
{#if child.type === "file"}
{formatDataVolume(child.file_size, 3)}
{/if}
</td>
<td class="node_icons">
<div class="icons_wrap">
{#if child.abuse_type !== undefined}
<i class="icon" title="This file / directory has received an abuse report. It cannot be shared">block</i>
{:else if node_is_shared(child)}
<a
href="/d/{child.id}"
on:click={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
class="button action_button"
>
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
</a>
<table class="directory">
<thead>
<tr>
<td></td>
<td><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="name">Name</SortButton></td>
<td class="hide_small"><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</SortButton></td>
<td></td>
</tr>
</thead>
<tbody>
{#each $nav.children as child, index (child.path)}
<tr
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})}
class="node"
class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden}
>
<td>
<img src={fs_node_icon(child, 64, 64)} class="node_icon" class:large_icons alt="icon"/>
</td>
<td class="node_name">
<a href={"/d"+fs_encode_path(child.path)}>
{child.name}
</a>
</td>
<td class="node_size hide_small">
{#if child.type === "file"}
{formatDataVolume(child.file_size, 3)}
{/if}
{#if child.properties !== undefined && child.properties.branding_enabled === "true" && !hide_branding}
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Branding, original: e})}>
<i class="icon">palette</i>
</button>
{/if}
{#if !hide_edit}
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}>
<i class="icon">menu</i>
</button>
{/if}
</div>
</td>
</a>
{/each}
</div>
</td>
<td class="node_icons">
<div class="icons_wrap">
{#if child.abuse_type !== undefined}
<i class="icon" title="This file / directory has received an abuse report. It cannot be shared">block</i>
{:else if node_is_shared(child)}
<a
href="/d/{child.id}"
onclick={e => dispatch("file", {index: index, action: FileAction.Share, original: e})}
class="button action_button"
>
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
</a>
{/if}
{#if child.properties !== undefined && child.properties.branding_enabled === "true" && !hide_branding}
<button class="action_button" onclick={e => dispatch("file", {index: index, action: FileAction.Branding, original: e})}>
<i class="icon">palette</i>
</button>
{/if}
{#if !hide_edit}
<button class="action_button" onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}>
<i class="icon">menu</i>
</button>
{/if}
</div>
</td>
</tr>
{/each}
</tbody>
</table>
<style>
.directory {

View File

@@ -1,19 +1,23 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { onMount } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
let search_bar: HTMLInputElement
let error = ""
let search_term = ""
let search_results: string[] = []
let selected_result = 0
let search_bar: HTMLInputElement = $state()
let error = $state("")
let search_term = $state("")
let search_results: string[] = $state([])
let selected_result = $state(0)
let searching = false
let last_searched_term = ""
let last_limit = 10
let last_limit = $state(10)
onMount(() => {
// Clear results when the user moves to a new directory
@@ -144,7 +148,7 @@ const window_keydown = (e: KeyboardEvent) => {
}
</script>
<svelte:window on:keydown={window_keydown} />
<svelte:window onkeydown={window_keydown} />
{#if error === "path_not_found" || error === "node_is_a_directory"}
<div class="highlight_yellow center">
@@ -160,7 +164,7 @@ const window_keydown = (e: KeyboardEvent) => {
{/if}
<div class="center">
<form class="search_form" on:submit|preventDefault={submit_search}>
<form class="search_form" onsubmit={preventDefault(submit_search)}>
<i class="icon">search</i>
<input
bind:this={search_bar}
@@ -169,12 +173,12 @@ const window_keydown = (e: KeyboardEvent) => {
placeholder="Press / to search in {$nav.base.name}"
style="width: 100%;"
bind:value={search_term}
on:keydown={input_keydown}
on:keyup={input_keyup}
onkeydown={input_keydown}
onkeyup={input_keyup}
/>
{#if search_term !== ""}
<!-- Button needs to be of button type in order to not submit the form -->
<button on:click={() => clear_search(false)} type="button">
<button onclick={() => clear_search(false)} type="button">
<i class="icon">close</i>
</button>
{/if}
@@ -188,7 +192,7 @@ const window_keydown = (e: KeyboardEvent) => {
{#each search_results as result, index}
<a
href={"/d"+fs_encode_path(result)}
on:click|preventDefault={() => open_result(index)}
onclick={preventDefault(() => open_result(index))}
class="node"
class:node_selected={selected_result === index}
>
@@ -203,7 +207,7 @@ const window_keydown = (e: KeyboardEvent) => {
{#if search_results.length === last_limit}
<div class="node">
<div class="node_name" style="text-align: center;">
<button on:click={() => {search(last_limit + 100)}}>
<button onclick={() => {search(last_limit + 100)}}>
<i class="icon">expand_more</i>
More results
</button>