Convert multiple pages into SPA

This commit is contained in:
2025-10-09 15:48:23 +02:00
parent c616b2da7f
commit 06d04a1abc
110 changed files with 1245 additions and 1319 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { fs_encode_path, fs_node_icon, node_is_shared } from "filesystem/FilesystemAPI"
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI"
import type { FSNavigator } from "filesystem/FSNavigator";
import { FileAction } from "./FileManagerLib";
@@ -36,21 +36,14 @@ export let hide_edit = false
</a>
{/if}
{#if $nav.permissions.write && !hide_edit}
<button
class="action_button flat"
on:click={e => dispatch("file", {index: index, action: FileAction.Edit, original: e})}
>
<i class="icon">edit</i>
</button>
{/if}
{#if !hide_edit}
<button
class="action_button flat"
on:click={e => dispatch("file", {index: index, action: FileAction.Download, original: e})}
on:click={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}
>
<i class="icon">save</i>
<i class="icon">menu</i>
</button>
{/if}
</a>
{/each}
</div>
@@ -71,9 +64,9 @@ export let hide_edit = false
color: var(--body_text-color);
padding: 2px;
align-items: center;
background: var(--input_background);
background: var(--shaded_background);
backdrop-filter: blur(4px);
border-radius: 4px;
box-shadow: 1px 1px 8px 0px var(--shadow_color);
gap: 6px;
}
.node:hover:not(.node_selected) {
@@ -92,7 +85,6 @@ export let hide_edit = false
height: 2em;
width: 2em;
vertical-align: middle;
border-radius: 4px;
}
.node_name {
flex: 1 1 content;

View File

@@ -1,8 +1,9 @@
<script lang="ts">
import { onMount } from "svelte";
import { fs_mkdir } from "filesystem/FilesystemAPI";
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
@@ -14,7 +15,7 @@ let create_dir = async () => {
form.append("type", "dir")
try {
nav.set_loading(true)
loading_start()
await fs_mkdir(nav.base.path+"/"+new_dir_name)
new_dir_name = "" // Clear input field
error_msg = "" // Clear error msg
@@ -26,7 +27,7 @@ let create_dir = async () => {
error_msg = "Server returned an error: code: '"+err.value+"' message: "+err.message
}
} finally {
nav.set_loading(false)
loading_finish()
}
}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "filesystem/FilesystemAPI"
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "lib/FilesystemAPI"
import { onMount } from "svelte"
import CreateDirectory from "./CreateDirectory.svelte"
import ListView from "./ListView.svelte"
@@ -13,6 +13,7 @@ import type { FSNavigator } from "filesystem/FSNavigator";
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import { FileAction, type FileEvent } from "./FileManagerLib";
import FileMenu from "./FileMenu.svelte";
export let nav: FSNavigator
export let upload_widget: FsUploadWidget
@@ -23,6 +24,7 @@ let uploader: FsUploadWidget
let mode = "viewing"
let creating_dir = false
let show_hidden = false
let file_menu: FileMenu
export const upload = (files: File[]) => {
return uploader.upload(files)
@@ -84,6 +86,11 @@ const file_event = (e: CustomEvent<FileEvent>) => {
e.detail.original.stopPropagation()
fs_download(nav.children[index])
break
case FileAction.Menu:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
file_menu.open(nav.children[index], e.detail.original.target)
break
}
}
@@ -410,10 +417,10 @@ onMount(() => {
{/if}
</div>
<FileMenu bind:this={file_menu} bind:nav bind:edit_window />
<style>
.container {
height: 100%;
width: 100%;
padding: 0;
overflow: auto;
display: block;

View File

@@ -3,4 +3,4 @@ export type FileEvent = {
action: FileAction,
original: MouseEvent,
}
export enum FileAction { Click, Context, Edit, Share, Branding, Select, Download }
export enum FileAction { Click, Context, Edit, Share, Branding, Select, Download, Menu }

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import Button from "layout/Button.svelte";
import Dialog from "layout/Dialog.svelte";
import { bookmark_add, bookmark_del, bookmarks_store } from "lib/Bookmarks";
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
let is_bookmark: boolean = false
export const open = async (n: FSNode, target: EventTarget) => {
node = n
is_bookmark = false
for (const bm of $bookmarks_store) {
console.log(bm)
if (bm.id === n.id) {
is_bookmark = true
break
}
}
// Wait for the view to update, so the dialog gets the proper measurements
await tick()
dialog.open((target as Element).closest("button").getBoundingClientRect())
}
const bookmark = () => {
bookmark_add({
id: node.id,
icon: "folder_shared",
label: node.name,
})
}
</script>
<Dialog bind:this={dialog}>
<div class="menu">
<Button click={() => {dialog.close(); fs_download(node)}} icon="save" label="Download"/>
{#if is_bookmark}
<Button click={() => {dialog.close(); bookmark_del(node.id)}} icon="bookmark_remove" label="Remove bookmark"/>
{:else}
<Button click={() => {dialog.close(); bookmark()}} icon="bookmark_add" label="Add bookmark"/>
{/if}
{#if $nav.permissions.write}
<Button click={() => {dialog.close(); edit_window.edit(node, false, "file")}} icon="edit" label="Edit"/>
<Button click={() => {dialog.close(); edit_window.edit(node, false, "share")}} icon="share" label="Share"/>
<Button click={() => {dialog.close(); edit_window.edit(node, false, "branding")}} icon="palette" label="Branding"/>
{/if}
</div>
</Dialog>
<style>
.menu {
display: flex;
flex-direction: column;
max-width: 20em;
}
</style>

View File

@@ -4,17 +4,15 @@ import ListView from "./ListView.svelte"
import GalleryView from "./GalleryView.svelte"
import CompactView from "./CompactView.svelte"
import Modal from "util/Modal.svelte";
import LoadingIndicator from "util/LoadingIndicator.svelte";
import Breadcrumbs from "filesystem/Breadcrumbs.svelte"
import { FSNavigator } from "filesystem/FSNavigator";
import type { FSNode } from "filesystem/FilesystemAPI";
import type { FSNode } from "lib/FilesystemAPI";
import { FileAction, type FileEvent } from "./FileManagerLib";
let nav = new FSNavigator(false)
let modal: Modal
let dispatch = createEventDispatcher()
let directory_view = ""
let loading = false
let large_icons = false
let show_hidden = false
export let select_multiple = false
@@ -194,8 +192,6 @@ onMount(() => {
on:file={file_event}
/>
{/if}
<LoadingIndicator loading={loading}/>
</Modal>
<style>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { fs_node_icon, fs_node_type, fs_encode_path } from "filesystem/FilesystemAPI";
import { fs_node_icon, fs_node_type, fs_encode_path } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator";
import { FileAction } from "./FileManagerLib";
let dispatch = createEventDispatcher()
@@ -46,15 +46,15 @@ export let large_icons = false
width: 150px;
height: 150px;
overflow: hidden;
border-radius: 8px;
background: var(--input_background);
background: var(--shaded_background);
backdrop-filter: blur(4px);
border-radius: 4px;
color: var(--input_text);
display: flex;
flex-direction: column;
transition: background 0.2s;
text-decoration: none;
padding: 3px;
box-shadow: 1px 1px 0px 0px var(--shadow_color);
}
.file.large_icons {
width: 200px;
@@ -87,7 +87,7 @@ export let large_icons = false
}
.node_icon {
flex: 1 1 0;
border-radius: 6px;
border-radius: 4px;
background-position: center;
background-size: contain;
background-repeat: no-repeat;

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { formatDataVolume } from "util/Formatting";
import { fs_encode_path, fs_node_icon, node_is_shared } from "filesystem/FilesystemAPI"
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI"
import type { FSNavigator } from "filesystem/FSNavigator";
import SortButton from "layout/SortButton.svelte";
import { FileAction } from "./FileManagerLib";
@@ -19,7 +19,7 @@ export let hide_branding = false
<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><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</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)}
@@ -60,14 +60,11 @@ export let hide_branding = false
<i class="icon">palette</i>
</button>
{/if}
{#if $nav.permissions.write && !hide_edit}
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Edit, original: e})}>
<i class="icon">edit</i>
{#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}
<button class="action_button" on:click={e => dispatch("file", {index: index, action: FileAction.Download, original: e})}>
<i class="icon">save</i>
</button>
</div>
</td>
</a>
@@ -77,13 +74,11 @@ export let hide_branding = false
<style>
.directory {
display: table;
margin: 8px auto 16px auto;
background: var(--shaded_background);
border-collapse: collapse;
border-radius: 8px;
max-width: 99%;
width: 1200px;
backdrop-filter: blur(4px);
max-width: 1200px;
margin: auto; /* center */
}
.directory > * {
display: table-row;
@@ -116,7 +111,7 @@ td {
height: 32px;
width: 32px;
vertical-align: middle;
border-radius: 4px;
/* border-radius: 4px; */
margin: 2px;
}
.node_name {

View File

@@ -1,7 +1,8 @@
<script lang="ts">
import { onMount } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "filesystem/FilesystemAPI";
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";
export let nav: FSNavigator
@@ -43,9 +44,9 @@ const search = async (limit = 10) => {
last_limit = limit
searching = true
nav.set_loading(true)
try {
loading_start()
search_results = await fs_search(nav.base.path, search_term, limit)
} catch (err) {
if (err.value) {
@@ -54,6 +55,8 @@ const search = async (limit = 10) => {
alert(err)
console.error(err)
}
} finally {
loading_finish()
}
if (search_results.length > 0 && selected_result > search_results.length-1) {
@@ -61,7 +64,6 @@ const search = async (limit = 10) => {
}
searching = false
nav.set_loading(false)
// It's possible that the user entered another letter while we were
// performing the search reqeust. If this happens we run the search function
@@ -218,7 +220,6 @@ const window_keydown = (e: KeyboardEvent) => {
max-width: 100%;
padding-top: 2px;
padding-bottom: 2px;
border-bottom: 1px solid var(--separator);
}
.search_form {