Replace event dispatcher with a callback in filemanager

This commit is contained in:
2025-10-13 23:20:42 +02:00
parent 6d89c5ddd9
commit 75d9ed3023
41 changed files with 326 additions and 255 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from 'svelte/legacy';
import { fs_encode_path, node_is_shared } from "lib/FilesystemAPI"; import { fs_encode_path } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "./FSNavigator"; import type { FSNavigator } from "./FSNavigator";
let { nav }: { let { nav }: {
@@ -17,7 +17,7 @@ let { nav }: {
> >
{#if node.abuse_type !== undefined} {#if node.abuse_type !== undefined}
<i class="icon small">block</i> <i class="icon small">block</i>
{:else if node_is_shared(node)} {:else if node.is_shared()}
<i class="icon small">share</i> <i class="icon small">share</i>
{/if} {/if}
<div class="node_name" class:base={$nav.base_index === i}> <div class="node_name" class:base={$nav.base_index === i}>

View File

@@ -3,7 +3,7 @@ import { run } from 'svelte/legacy';
import Chart from "util/Chart.svelte"; import Chart from "util/Chart.svelte";
import { formatDataVolume, formatDate, formatThousands } from "util/Formatting"; import { formatDataVolume, formatDate, formatThousands } from "util/Formatting";
import Modal from "util/Modal.svelte"; import Modal from "util/Modal.svelte";
import { fs_path_url, fs_share_hotlink_url, fs_share_url, fs_timeseries, type FSNode } from "lib/FilesystemAPI"; import { fs_path_url, fs_share_hotlink_url, fs_share_url, fs_timeseries, type FSNode } from "lib/FilesystemAPI.svelte";
import { color_by_name } from "util/Util"; import { color_by_name } from "util/Util";
import { tick } from "svelte"; import { tick } from "svelte";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
@@ -86,7 +86,7 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
display: true, display: true,
position: "right", position: "right",
ticks: { ticks: {
callback: function (value, index, values) { callback: function (value: number, index, values) {
return formatDataVolume(value, 3); return formatDataVolume(value, 3);
}, },
}, },

View File

@@ -1,6 +1,6 @@
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
import { fs_get_node, fs_encode_path, fs_split_path } from "../lib/FilesystemAPI"; import { fs_get_node, fs_encode_path, fs_split_path } from "../lib/FilesystemAPI.svelte";
import type { FSNode, FSPath, FSPermissions, FSContext } from "../lib/FilesystemAPI"; import type { FSNode, FSPath, FSPermissions, FSContext } from "../lib/FilesystemAPI.svelte";
export class FSNavigator { export class FSNavigator {
// Parts of the raw API response // Parts of the raw API response

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { formatDataVolume, formatThousands } from "util/Formatting" import { formatDataVolume, formatThousands } from "util/Formatting"
import { fs_path_url } from "lib/FilesystemAPI"; import { fs_path_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "./FSNavigator"; import type { FSNavigator } from "./FSNavigator";
let { nav }: { let { nav }: {

View File

@@ -6,7 +6,7 @@ import Breadcrumbs from "./Breadcrumbs.svelte";
import DetailsWindow from "./DetailsWindow.svelte"; import DetailsWindow from "./DetailsWindow.svelte";
import FilePreview from "./viewers/FilePreview.svelte"; import FilePreview from "./viewers/FilePreview.svelte";
import FSUploadWidget from "./upload_widget/FSUploadWidget.svelte"; import FSUploadWidget from "./upload_widget/FSUploadWidget.svelte";
import { fs_download, type FSPath } from "lib/FilesystemAPI"; import { fs_download, type FSPath } from "lib/FilesystemAPI.svelte";
import { FSNavigator } from "./FSNavigator" import { FSNavigator } from "./FSNavigator"
import { css_from_path } from "filesystem/edit_window/Branding"; import { css_from_path } from "filesystem/edit_window/Branding";
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte"; import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { FSNavigator } from "./FSNavigator"; import type { FSNavigator } from "./FSNavigator";
import { fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, node_is_shared, type FSNode, type FSPermissions } from "lib/FilesystemAPI"; import { fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, type FSNode, type FSPermissions } from "lib/FilesystemAPI.svelte";
import { copy_text } from "util/Util"; import { copy_text } from "util/Util";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
import Dialog from "layout/Dialog.svelte"; import Dialog from "layout/Dialog.svelte";
@@ -38,7 +38,7 @@ export const open = async (e: MouseEvent, p: FSNode[]) => {
} }
const make_public = async () => { const make_public = async () => {
if (!node_is_shared(base)) { if (!base.is_shared()) {
base = await fs_update( base = await fs_update(
base.path, base.path,
{link_permissions: {read: true} as FSPermissions}, {link_permissions: {read: true} as FSPermissions},

View File

@@ -4,7 +4,7 @@ import { copy_text } from "util/Util";
import FileStats from "./FileStats.svelte"; import FileStats from "./FileStats.svelte";
import type { FSNavigator } from "./FSNavigator"; import type { FSNavigator } from "./FSNavigator";
import EditWindow from "./edit_window/EditWindow.svelte"; import EditWindow from "./edit_window/EditWindow.svelte";
import { fs_share_url, path_is_shared } from "lib/FilesystemAPI"; import { fs_share_url, path_is_shared } from "lib/FilesystemAPI.svelte";
import ShareDialog from "./ShareDialog.svelte"; import ShareDialog from "./ShareDialog.svelte";
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks"; import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI"; import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI.svelte";
import PermissionButton from "./PermissionButton.svelte"; import PermissionButton from "./PermissionButton.svelte";
let { let {

View File

@@ -2,7 +2,7 @@ import parse from "pure-color/parse";
import rgb2hsl from "pure-color/convert/rgb2hsl"; import rgb2hsl from "pure-color/convert/rgb2hsl";
import hsl2rgb from "pure-color/convert/hsl2rgb"; import hsl2rgb from "pure-color/convert/hsl2rgb";
import rgb2hex from "pure-color/convert/rgb2hex"; import rgb2hex from "pure-color/convert/rgb2hex";
import type { FSNode, FSNodeProperties } from "lib/FilesystemAPI"; import type { FSNode, FSNodeProperties } from "lib/FilesystemAPI.svelte";
type Style = { type Style = {
input_background: string, input_background: string,

View File

@@ -2,7 +2,7 @@
import { run } from 'svelte/legacy'; import { run } from 'svelte/legacy';
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import ThemePresets from "./ThemePresets.svelte"; import ThemePresets from "./ThemePresets.svelte";
import { fs_update, fs_node_type, type FSNode, type NodeOptions, node_is_shared, type FSPermissions } from "lib/FilesystemAPI"; import { fs_update, fs_node_type, type FSNode, type NodeOptions, type FSPermissions } from "lib/FilesystemAPI.svelte";
import CustomBanner from "filesystem/viewers/CustomBanner.svelte"; import CustomBanner from "filesystem/viewers/CustomBanner.svelte";
import HelpButton from "layout/HelpButton.svelte"; import HelpButton from "layout/HelpButton.svelte";
import FilePicker from "filesystem/filemanager/FilePicker.svelte"; import FilePicker from "filesystem/filemanager/FilePicker.svelte";
@@ -49,7 +49,7 @@ const handle_picker = async (e: CustomEvent<FSNode[]>) => {
} }
// If this image is not public, it will be made public // If this image is not public, it will be made public
if (!node_is_shared(f)) { if (!f.is_shared()) {
try { try {
f = await fs_update( f = await fs_update(
e.detail[0].path, e.detail[0].path,

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from 'svelte/legacy';
import { fs_rename, fs_update, type FSNode, type NodeOptions } from "lib/FilesystemAPI"; import { fs_rename, fs_update, type FSNode, type NodeOptions } from "lib/FilesystemAPI.svelte";
import Modal from "util/Modal.svelte"; import Modal from "util/Modal.svelte";
import BrandingOptions from "./BrandingOptions.svelte"; import BrandingOptions from "./BrandingOptions.svelte";
import { branding_from_node } from "./Branding"; import { branding_from_node } from "./Branding";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
import { fs_delete_all, type FSNode } from "lib/FilesystemAPI"; import { fs_delete_all, type FSNode } from "lib/FilesystemAPI.svelte";
import PathLink from "filesystem/util/PathLink.svelte"; import PathLink from "filesystem/util/PathLink.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import ToggleButton from "layout/ToggleButton.svelte"; import ToggleButton from "layout/ToggleButton.svelte";
import type { FSPermissions } from "lib/FilesystemAPI"; import type { FSPermissions } from "lib/FilesystemAPI.svelte";
let { let {
permissions = $bindable() permissions = $bindable()

View File

@@ -3,7 +3,7 @@ import { run } from 'svelte/legacy';
import { domain_url } from "util/Util"; import { domain_url } from "util/Util";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
import { formatDate } from "util/Formatting"; import { formatDate } from "util/Formatting";
import { node_is_shared, type FSNode, type NodeOptions } from "lib/FilesystemAPI"; import { type FSNode, type NodeOptions } from "lib/FilesystemAPI.svelte";
import AccessControl from "./AccessControl.svelte"; import AccessControl from "./AccessControl.svelte";
let { let {
@@ -18,7 +18,7 @@ let embed_html: string = $state()
let preview_area: HTMLDivElement = $state() let preview_area: HTMLDivElement = $state()
const embed_iframe = (file: FSNode, options: NodeOptions) => { const embed_iframe = (file: FSNode, options: NodeOptions) => {
if (!node_is_shared(file)) { if (!file.is_shared()) {
example = false example = false
embed_html = "File is not shared, can't generate embed code" embed_html = "File is not shared, can't generate embed code"
return return
@@ -34,7 +34,7 @@ const embed_iframe = (file: FSNode, options: NodeOptions) => {
let example = $state(false) let example = $state(false)
const toggle_example = () => { const toggle_example = () => {
if (node_is_shared(file)) { if (file.is_shared()) {
example = !example example = !example
if (example) { if (example) {
preview_area.innerHTML = embed_html preview_area.innerHTML = embed_html
@@ -86,7 +86,7 @@ run(() => {
<textarea bind:value={embed_html} style="width: 100%; height: 4em;"></textarea> <textarea bind:value={embed_html} style="width: 100%; height: 4em;"></textarea>
<br/> <br/>
<CopyButton text={embed_html}>Copy HTML</CopyButton> <CopyButton text={embed_html}>Copy HTML</CopyButton>
<button onclick={toggle_example} class:button_highlight={example} disabled={!node_is_shared(file)}> <button onclick={toggle_example} class:button_highlight={example} disabled={!file.is_shared()}>
<i class="icon">visibility</i> Show example <i class="icon">visibility</i> Show example
</button> </button>
</div> </div>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { FSNodeProperties } from "lib/FilesystemAPI"; import type { FSNodeProperties } from "lib/FilesystemAPI.svelte";
let { let {
properties = $bindable({} as FSNodeProperties) properties = $bindable({} as FSNodeProperties)

View File

@@ -1,21 +1,20 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { fs_encode_path, fs_node_icon } from "lib/FilesystemAPI.svelte"
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI"
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { FileAction } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
let dispatch = createEventDispatcher()
let { let {
nav, nav,
file_event,
show_hidden = false, show_hidden = false,
large_icons = false, large_icons = false,
hide_edit = false hide_edit = false
}: { }: {
nav: FSNavigator; nav: FSNavigator;
show_hidden?: boolean; file_event: FileActionHandler
large_icons?: boolean; show_hidden?: boolean
hide_edit?: boolean; large_icons?: boolean
hide_edit?: boolean
} = $props(); } = $props();
</script> </script>
@@ -23,8 +22,8 @@ let {
{#each $nav.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)}
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})} onclick={e => file_event(FileAction.Click, index, e)}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})} oncontextmenu={e => file_event(FileAction.Context, index, e)}
class="node" class="node"
class:node_selected={child.fm_selected} class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden} class:hidden={child.name.startsWith(".") && !show_hidden}
@@ -34,14 +33,14 @@ let {
{child.name} {child.name}
</div> </div>
{#if node_is_shared(child)} {#if child.is_shared()}
<i class="icon" title="This file / directory is shared. Click to open public link">share</i> <i class="icon" title="This file / directory is shared. Click to open public link">share</i>
{/if} {/if}
{#if !hide_edit} {#if !hide_edit}
<button <button
class="action_button flat" class="action_button flat"
onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})} onclick={e => file_event(FileAction.Menu, index, e)}
> >
<i class="icon">menu</i> <i class="icon">menu</i>
</button> </button>
@@ -66,8 +65,8 @@ let {
color: var(--body_text-color); color: var(--body_text-color);
padding: 2px; padding: 2px;
align-items: center; align-items: center;
background: var(--shaded_background); background: var(--body_background);
backdrop-filter: blur(4px); /* backdrop-filter: blur(4px); */
border-radius: 4px; border-radius: 4px;
gap: 6px; gap: 6px;
} }

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from 'svelte/legacy';
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fs_mkdir } from "lib/FilesystemAPI"; import { fs_mkdir } from "lib/FilesystemAPI.svelte";
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy'; import { run } from 'svelte/legacy';
import { fs_delete_all, fs_download, fs_rename, type FSNode } from "lib/FilesystemAPI" import { fs_delete_all, fs_rename, type FSNode } from "lib/FilesystemAPI.svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import CreateDirectory from "./CreateDirectory.svelte" import CreateDirectory from "./CreateDirectory.svelte"
import ListView from "./ListView.svelte" import ListView from "./ListView.svelte"
@@ -13,7 +13,7 @@ import SearchBar from "./SearchBar.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte"; import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte"; import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import { FileAction, type FileEvent } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
import FileMenu from "./FileMenu.svelte"; import FileMenu from "./FileMenu.svelte";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
@@ -43,13 +43,12 @@ export const upload = (files: File[]) => {
} }
// Navigation functions // Navigation functions
const file_event = (e: CustomEvent<FileEvent>) => { const file_event: FileActionHandler = (action: FileAction, index: number, orig: Event) => {
const index = e.detail.index orig.preventDefault()
orig.stopPropagation()
switch (e.detail.action) { switch (action) {
case FileAction.Click: case FileAction.Click:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
creating_dir = false creating_dir = false
if (mode === "viewing") { if (mode === "viewing") {
@@ -68,40 +67,26 @@ const file_event = (e: CustomEvent<FileEvent>) => {
case FileAction.Context: case FileAction.Context:
// If this is a touch event we will select the item // If this is a touch event we will select the item
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) {
e.detail.original.preventDefault()
select_node(index) select_node(index)
} else {
file_menu.open(nav.children[index], orig.target)
} }
break break
case FileAction.Edit: case FileAction.Edit:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
edit_window.edit(nav.children[index], false, "file") edit_window.edit(nav.children[index], false, "file")
break break
case FileAction.Share: case FileAction.Share:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
creating_dir = false creating_dir = false
edit_window.edit(nav.children[index], false, "share") edit_window.edit(nav.children[index], false, "share")
break break
case FileAction.Branding: case FileAction.Branding:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
edit_window.edit(nav.children[index], false, "branding") edit_window.edit(nav.children[index], false, "branding")
break break
case FileAction.Select: case FileAction.Select:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
select_node(index) select_node(index)
break break
case FileAction.Download:
e.detail.original.preventDefault()
e.detail.original.stopPropagation()
fs_download(nav.children[index])
break
case FileAction.Menu: case FileAction.Menu:
e.detail.original.preventDefault() file_menu.open(nav.children[index], orig.target)
e.detail.original.stopPropagation()
file_menu.open(nav.children[index], e.detail.original.target)
break break
} }
} }
@@ -423,11 +408,11 @@ run(() => {
{@render children?.()} {@render children?.()}
{#if directory_view === "list"} {#if directory_view === "list"}
<ListView nav={nav} show_hidden={show_hidden} large_icons={large_icons} on:file={file_event} /> <ListView nav={nav} file_event={file_event} show_hidden={show_hidden} large_icons={large_icons}/>
{:else if directory_view === "gallery"} {:else if directory_view === "gallery"}
<GalleryView nav={nav} show_hidden={show_hidden} large_icons={large_icons} on:file={file_event} /> <GalleryView nav={nav} file_event={file_event} show_hidden={show_hidden} large_icons={large_icons}/>
{:else if directory_view === "compact"} {:else if directory_view === "compact"}
<CompactView nav={nav} show_hidden={show_hidden} large_icons={large_icons} on:file={file_event} /> <CompactView nav={nav} file_event={file_event} show_hidden={show_hidden} large_icons={large_icons}/>
{/if} {/if}
</div> </div>
@@ -447,8 +432,8 @@ run(() => {
width: 100%; width: 100%;
margin: auto; margin: auto;
padding: 0; padding: 0;
background: var(--shaded_background); background: var(--body_background);
backdrop-filter: blur(4px); /* backdrop-filter: blur(4px); */
} }
.toolbar { .toolbar {
display: flex; display: flex;

View File

@@ -3,4 +3,18 @@ export type FileEvent = {
action: FileAction, action: FileAction,
original: MouseEvent, original: MouseEvent,
} }
export enum FileAction { Click, Context, Edit, Share, Branding, Select, Download, Menu } export enum FileAction {
Click,
Context,
Edit,
Share,
Branding,
Select,
Download,
Menu,
}
export type FileActionHandler = (
action: FileAction,
file_index: number,
original_event: Event,
) => void

View File

@@ -4,7 +4,8 @@ import type { FSNavigator } from "filesystem/FSNavigator";
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
import Dialog from "layout/Dialog.svelte"; import Dialog from "layout/Dialog.svelte";
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks"; import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";
import { fs_download, type FSNode } from "lib/FilesystemAPI"; import { fs_delete, fs_download, type FSNode } from "lib/FilesystemAPI.svelte";
import { loading_finish, loading_start } from "lib/Loading";
import { tick } from "svelte"; import { tick } from "svelte";
let { let {
@@ -20,10 +21,26 @@ let node: FSNode = $state(null)
export const open = async (n: FSNode, target: EventTarget) => { export const open = async (n: FSNode, target: EventTarget) => {
node = n node = n
let el: HTMLElement = (target as Element).closest("button")
if (el === null) {
el = (target as Element).closest("a")
}
// Wait for the view to update, so the dialog gets the proper measurements // Wait for the view to update, so the dialog gets the proper measurements
await tick() await tick()
dialog.open((target as Element).closest("button").getBoundingClientRect()) dialog.open(el.getBoundingClientRect())
}
const delete_node = async () => {
try {
loading_start()
await fs_delete(node.path)
nav.reload()
} catch (err) {
alert(JSON.stringify(err))
} finally {
loading_finish()
}
} }
</script> </script>
@@ -36,6 +53,7 @@ export const open = async (n: FSNode, target: EventTarget) => {
<Button click={() => {dialog.close(); bookmark_add(node)}} icon="bookmark_add" label="Add bookmark"/> <Button click={() => {dialog.close(); bookmark_add(node)}} icon="bookmark_add" label="Add bookmark"/>
{/if} {/if}
{#if $nav.permissions.write} {#if $nav.permissions.write}
<Button click={() => {dialog.close(); delete_node()}} icon="delete" label="Delete"/>
<Button click={() => {dialog.close(); edit_window.edit(node, false, "file")}} icon="edit" label="Edit"/> <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, "share")}} icon="share" label="Share"/>
<Button click={() => {dialog.close(); edit_window.edit(node, false, "branding")}} icon="palette" label="Branding"/> <Button click={() => {dialog.close(); edit_window.edit(node, false, "branding")}} icon="palette" label="Branding"/>

View File

@@ -6,8 +6,8 @@ import CompactView from "./CompactView.svelte"
import Modal from "util/Modal.svelte"; import Modal from "util/Modal.svelte";
import Breadcrumbs from "filesystem/Breadcrumbs.svelte" import Breadcrumbs from "filesystem/Breadcrumbs.svelte"
import { FSNavigator } from "filesystem/FSNavigator"; import { FSNavigator } from "filesystem/FSNavigator";
import type { FSNode } from "lib/FilesystemAPI"; import type { FSNode } from "lib/FilesystemAPI.svelte";
import { FileAction, type FileEvent } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
let nav = $state(new FSNavigator(false)) let nav = $state(new FSNavigator(false))
let modal: Modal = $state() let modal: Modal = $state()
@@ -34,12 +34,10 @@ let selected_files = $derived($nav.children.reduce((acc, file) => {
// Navigation functions // Navigation functions
const file_event = (e: CustomEvent<FileEvent>) => { const file_event: FileActionHandler = (action: FileAction, index: number, orig: Event) => {
const index = e.detail.index switch (action) {
switch (e.detail.action) {
case FileAction.Click: case FileAction.Click:
e.detail.original.preventDefault() orig.preventDefault()
if (nav.children[index].type === "dir") { if (nav.children[index].type === "dir") {
nav.navigate(nav.children[index].path, true) nav.navigate(nav.children[index].path, true)
} else { } else {
@@ -49,12 +47,12 @@ const file_event = (e: CustomEvent<FileEvent>) => {
case FileAction.Context: case FileAction.Context:
// If this is a touch event we will select the item // If this is a touch event we will select the item
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) {
e.detail.original.preventDefault() orig.preventDefault()
select_node(index) select_node(index)
} }
break break
case FileAction.Select: case FileAction.Select:
e.detail.original.preventDefault() orig.preventDefault()
nav.children[index].fm_selected = !nav.children[index].fm_selected nav.children[index].fm_selected = !nav.children[index].fm_selected
break break
} }
@@ -134,7 +132,7 @@ onMount(() => {
<svelte:window onkeydown={detect_shift} onkeyup={detect_shift} /> <svelte:window onkeydown={detect_shift} onkeyup={detect_shift} />
<Modal bind:this={modal} width="900px"> <Modal bind:this={modal} width="900px">
{#snippet title()} {#snippet header()}
<div class="header" > <div class="header" >
<button class="button round" onclick={modal.hide}> <button class="button round" onclick={modal.hide}>
<i class="icon">close</i> <i class="icon">close</i>
@@ -175,26 +173,26 @@ onMount(() => {
{#if directory_view === "list"} {#if directory_view === "list"}
<ListView <ListView
nav={nav} nav={nav}
file_event={file_event}
show_hidden={show_hidden} show_hidden={show_hidden}
large_icons={large_icons} large_icons={large_icons}
hide_edit hide_edit
hide_branding hide_branding
on:file={file_event}
/> />
{:else if directory_view === "gallery"} {:else if directory_view === "gallery"}
<GalleryView <GalleryView
nav={nav} nav={nav}
file_event={file_event}
show_hidden={show_hidden} show_hidden={show_hidden}
large_icons={large_icons} large_icons={large_icons}
on:file={file_event}
/> />
{:else if directory_view === "compact"} {:else if directory_view === "compact"}
<CompactView <CompactView
nav={nav} nav={nav}
file_event={file_event}
show_hidden={show_hidden} show_hidden={show_hidden}
large_icons={large_icons} large_icons={large_icons}
hide_edit hide_edit
on:file={file_event}
/> />
{/if} {/if}
</Modal> </Modal>

View File

@@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte" import { fs_node_icon, fs_node_type, fs_encode_path } from "lib/FilesystemAPI.svelte";
import { fs_node_icon, fs_node_type, fs_encode_path } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { FileAction } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
let dispatch = createEventDispatcher()
let { let {
nav, nav,
file_event,
show_hidden = false, show_hidden = false,
large_icons = false large_icons = false
}: { }: {
nav: FSNavigator; nav: FSNavigator
show_hidden?: boolean; file_event: FileActionHandler
large_icons?: boolean; show_hidden?: boolean
large_icons?: boolean
} = $props(); } = $props();
</script> </script>
@@ -20,8 +20,8 @@ let {
{#each $nav.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)}
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})} onclick={e => file_event(FileAction.Click, index, e)}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})} oncontextmenu={e => file_event(FileAction.Context, index, e)}
class:selected={child.fm_selected} class:selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden} class:hidden={child.name.startsWith(".") && !show_hidden}
class:large_icons class:large_icons
@@ -52,8 +52,8 @@ let {
width: 150px; width: 150px;
height: 150px; height: 150px;
overflow: hidden; overflow: hidden;
background: var(--shaded_background); background: var(--body_background);
backdrop-filter: blur(4px); /* backdrop-filter: blur(4px); */
border-radius: 4px; border-radius: 4px;
color: var(--input_text); color: var(--input_text);
display: flex; display: flex;

View File

@@ -1,25 +1,24 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte";
import { formatDataVolume } from "util/Formatting"; import { formatDataVolume } from "util/Formatting";
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI" import { fs_encode_path, fs_node_icon } from "lib/FilesystemAPI.svelte"
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import SortButton from "layout/SortButton.svelte"; import SortButton from "layout/SortButton.svelte";
import { FileAction } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
let dispatch = createEventDispatcher()
let { let {
nav, nav,
file_event,
show_hidden = false, show_hidden = false,
large_icons = false, large_icons = false,
hide_edit = false, hide_edit = false,
hide_branding = false hide_branding = false
}: { }: {
nav: FSNavigator; nav: FSNavigator
show_hidden?: boolean; file_event: FileActionHandler
large_icons?: boolean; show_hidden?: boolean
hide_edit?: boolean; large_icons?: boolean
hide_branding?: boolean; hide_edit?: boolean
hide_branding?: boolean
} = $props(); } = $props();
</script> </script>
@@ -35,8 +34,8 @@ let {
<tbody> <tbody>
{#each $nav.children as child, index (child.path)} {#each $nav.children as child, index (child.path)}
<tr <tr
onclick={e => dispatch("file", {index: index, action: FileAction.Click, original: e})} onclick={e => file_event(FileAction.Click, index, e)}
oncontextmenu={e => dispatch("file", {index: index, action: FileAction.Context, original: e})} oncontextmenu={e => file_event(FileAction.Context, index, e)}
class="node" class="node"
class:node_selected={child.fm_selected} class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && !show_hidden} class:hidden={child.name.startsWith(".") && !show_hidden}
@@ -45,7 +44,7 @@ let {
<img src={fs_node_icon(child, 64, 64)} class="node_icon" class:large_icons alt="icon"/> <img src={fs_node_icon(child, 64, 64)} class="node_icon" class:large_icons alt="icon"/>
</td> </td>
<td class="node_name"> <td class="node_name">
<a href={"/d"+fs_encode_path(child.path)}> <a class="title_link" href={"/d"+fs_encode_path(child.path)}>
{child.name} {child.name}
</a> </a>
</td> </td>
@@ -58,22 +57,22 @@ let {
<div class="icons_wrap"> <div class="icons_wrap">
{#if child.abuse_type !== undefined} {#if child.abuse_type !== undefined}
<i class="icon" title="This file / directory has received an abuse report. It cannot be shared">block</i> <i class="icon" title="This file / directory has received an abuse report. It cannot be shared">block</i>
{:else if node_is_shared(child)} {:else if child.is_shared()}
<a <a
href="/d/{child.id}" href="/d/{child.id}"
onclick={e => dispatch("file", {index: index, action: FileAction.Share, original: e})} onclick={e => file_event(FileAction.Share, index, e)}
class="button action_button" class="button action_button"
> >
<i class="icon" title="This file / directory is shared. Click to open public link">share</i> <i class="icon" title="This file / directory is shared. Click to open public link">share</i>
</a> </a>
{/if} {/if}
{#if child.properties !== undefined && child.properties.branding_enabled === "true" && !hide_branding} {#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})}> <button class="action_button" onclick={e => file_event(FileAction.Branding, index, e)}>
<i class="icon">palette</i> <i class="icon">palette</i>
</button> </button>
{/if} {/if}
{#if !hide_edit} {#if !hide_edit}
<button class="action_button" onclick={e => dispatch("file", {index: index, action: FileAction.Menu, original: e})}> <button class="action_button" onclick={e => file_event(FileAction.Menu, index, e)}>
<i class="icon">menu</i> <i class="icon">menu</i>
</button> </button>
{/if} {/if}
@@ -87,9 +86,9 @@ let {
<style> <style>
.directory { .directory {
display: table; display: table;
background: var(--shaded_background); background: var(--body_background);
border-collapse: collapse; border-collapse: collapse;
backdrop-filter: blur(4px); /* backdrop-filter: blur(4px); */
max-width: 1200px; max-width: 1200px;
margin: auto; /* center */ margin: auto; /* center */
} }
@@ -99,11 +98,14 @@ let {
.directory > * > * { .directory > * > * {
display: table-cell; display: table-cell;
} }
td {
padding: 0;
}
.node { .node {
display: table-row; display: table-row;
text-decoration: none; text-decoration: none;
color: var(--body_text-color); color: var(--body_text-color);
padding: 6px; cursor: pointer;
} }
.node:not(:last-child) { .node:not(:last-child) {
border-bottom: 1px solid var(--separator); border-bottom: 1px solid var(--separator);
@@ -118,6 +120,7 @@ let {
color: var(--highlight_text_color); color: var(--highlight_text_color);
} }
td { td {
padding: 2px;
vertical-align: middle; vertical-align: middle;
} }
.node_icon { .node_icon {
@@ -132,6 +135,10 @@ td {
line-height: 1.2em; line-height: 1.2em;
word-break: break-all; word-break: break-all;
} }
.title_link {
text-decoration: none;
color: var(--body_text_color);
}
.node_size { .node_size {
min-width: 5em; min-width: 5em;
white-space: nowrap; white-space: nowrap;

View File

@@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI"; import { fs_search, fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
@@ -118,13 +116,17 @@ const input_keyup = (e: KeyboardEvent) => {
} }
// Submitting opens the selected result // Submitting opens the selected result
const submit_search = () => { const submit_search = (e: Event) => {
e.preventDefault()
if (search_results.length !== 0) { if (search_results.length !== 0) {
open_result(selected_result) open_result(selected_result)
} }
} }
const open_result = (index: number) => { const open_result = (index: number, e?: MouseEvent) => {
if (e !== undefined) {
e.preventDefault()
}
nav.navigate(search_results[index], true) nav.navigate(search_results[index], true)
clear_search(false) clear_search(false)
} }
@@ -164,7 +166,7 @@ const window_keydown = (e: KeyboardEvent) => {
{/if} {/if}
<div class="center"> <div class="center">
<form class="search_form" onsubmit={preventDefault(submit_search)}> <form class="search_form" onsubmit={submit_search}>
<i class="icon">search</i> <i class="icon">search</i>
<input <input
bind:this={search_bar} bind:this={search_bar}
@@ -192,7 +194,7 @@ const window_keydown = (e: KeyboardEvent) => {
{#each search_results as result, index} {#each search_results as result, index}
<a <a
href={"/d"+fs_encode_path(result)} href={"/d"+fs_encode_path(result)}
onclick={preventDefault(() => open_result(index))} onclick={(e) => open_result(index, e)}
class="node" class="node"
class:node_selected={selected_result === index} class:node_selected={selected_result === index}
> >

View File

@@ -9,7 +9,7 @@
// //
// on_error is called when the upload has failed. The parameters are the error // on_error is called when the upload has failed. The parameters are the error
import { fs_path_url, type GenericResponse } from "lib/FilesystemAPI" import { fs_path_url, type GenericResponse } from "lib/FilesystemAPI.svelte"
// code and an error message // code and an error message
export const upload_file = ( export const upload_file = (

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from 'svelte/legacy';
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { fs_path_url, fs_encode_path, fs_node_icon } from "lib/FilesystemAPI" import { fs_path_url, fs_encode_path, fs_node_icon } from "lib/FilesystemAPI.svelte"
import TextBlock from "layout/TextBlock.svelte" import TextBlock from "layout/TextBlock.svelte"
import type { FSNavigator } from 'filesystem/FSNavigator'; import type { FSNavigator } from 'filesystem/FSNavigator';

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { FSNode } from 'lib/FilesystemAPI'; import type { FSNode } from 'lib/FilesystemAPI.svelte';
import { run } from 'svelte/legacy'; import { run } from 'svelte/legacy';
let { path = [] }: {path: FSNode[]} = $props(); let { path = [] }: {path: FSNode[]} = $props();

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import IconBlock from "layout/IconBlock.svelte"; import IconBlock from "layout/IconBlock.svelte";
import { fs_thumbnail_url } from "lib/FilesystemAPI"; import { fs_thumbnail_url } from "lib/FilesystemAPI.svelte";
import TextBlock from "layout/TextBlock.svelte" import TextBlock from "layout/TextBlock.svelte"
import { formatDataVolume, formatDate } from "util/Formatting"; import { formatDataVolume, formatDate } from "util/Formatting";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount, 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 "lib/FilesystemAPI"; import { fs_node_type } from "lib/FilesystemAPI.svelte";
import FileManager from "filesystem/filemanager/FileManager.svelte"; import FileManager from "filesystem/filemanager/FileManager.svelte";
import Audio from "./Audio.svelte"; import Audio from "./Audio.svelte";
import File from "./File.svelte"; import File from "./File.svelte";

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { swipe_nav } from "lib/SwipeNavigate"; import { swipe_nav } from "lib/SwipeNavigate";
import { fs_path_url } from "lib/FilesystemAPI"; import { fs_path_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { fs_path_url } from "lib/FilesystemAPI"; import { fs_path_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
let { nav }: { let { nav }: {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { tick } from "svelte"; import { tick } from "svelte";
import { fs_path_url, type FSNode } from "lib/FilesystemAPI"; import { fs_path_url, type FSNode } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
let { nav, children }: { let { nav, children }: {

View File

@@ -19,7 +19,7 @@ import { formatDate } from "util/Formatting"
import TorrentItem from "./TorrentItem.svelte" import TorrentItem from "./TorrentItem.svelte"
import IconBlock from "layout/IconBlock.svelte"; import IconBlock from "layout/IconBlock.svelte";
import TextBlock from "layout/TextBlock.svelte" import TextBlock from "layout/TextBlock.svelte"
import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI"; import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI.svelte";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount, createEventDispatcher, tick } from "svelte"; import { onMount, createEventDispatcher, tick } from "svelte";
import { video_position } from "lib/VideoPosition"; import { video_position } from "lib/VideoPosition";
import { fs_path_url } from "lib/FilesystemAPI"; import { fs_path_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()

View File

@@ -13,7 +13,7 @@ import { formatDataVolume, formatDate } from "util/Formatting"
import ZipItem from "filesystem/viewers/ZipItem.svelte"; import ZipItem from "filesystem/viewers/ZipItem.svelte";
import IconBlock from "layout/IconBlock.svelte"; import IconBlock from "layout/IconBlock.svelte";
import TextBlock from "layout/TextBlock.svelte" import TextBlock from "layout/TextBlock.svelte"
import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI"; import { fs_node_icon, fs_path_url } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";

View File

@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
let { style = "" }: { let {
style: string; style = ""
}: {
style?: string;
} = $props(); } = $props();
</script> </script>

View File

@@ -1,7 +1,7 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { fs_check_response, fs_path_url, type FSNode } from "./FilesystemAPI" import { fs_check_response, fs_path_url, type FSNode } from "./FilesystemAPI.svelte"
import { loading_finish, loading_start } from "lib/Loading" import { loading_finish, loading_start } from "lib/Loading"
import { get_user } from "./PixeldrainAPI" import { get_user } from "lib/PixeldrainAPI"
const bookmarks_file = "/me/.fnx/bookmarks.json" const bookmarks_file = "/me/.fnx/bookmarks.json"

View File

@@ -6,15 +6,24 @@ export type GenericResponse = {
message: string, message: string,
} }
export type FSPath = { export class FSPath {
path: Array<FSNode>, path: Array<FSNode>
base_index: number, base_index: number
children: Array<FSNode>, children: Array<FSNode>
permissions: FSPermissions, permissions: FSPermissions
context: FSContext, context: FSContext
} }
export type FSNode = { export const path_is_shared = (path: FSNode[]): boolean => {
for (let i = 0; i < path.length; i++) {
if (path[i].is_shared()) {
return true
}
}
return false
}
export class FSNode {
type: string type: string
path: string path: string
name: string name: string
@@ -42,26 +51,13 @@ export type FSNode = {
// Added by us // Added by us
// Indicates whether the file is selected in the file manager // Indicates whether the file is selected in the file manager
fm_selected?: boolean fm_selected?: boolean = $state(false)
}
export const node_is_shared = (node: FSNode): boolean => { is_shared = (): boolean => {
if ( return (this.link_permissions !== undefined && this.link_permissions.read === true) ||
(node.link_permissions !== undefined && node.link_permissions.read === true) || (this.user_permissions !== undefined && Object.keys(this.user_permissions).length > 0) ||
(node.user_permissions !== undefined && Object.keys(node.user_permissions).length > 0) || (this.password_permissions !== undefined && Object.keys(this.password_permissions).length > 0)
(node.password_permissions !== undefined && Object.keys(node.password_permissions).length > 0)
) {
return true
} }
return false
}
export const path_is_shared = (path: FSNode[]): boolean => {
for (let i = 0; i < path.length; i++) {
if (node_is_shared(path[i])) {
return true
}
}
return false
} }
export type FSNodeProperties = { export type FSNodeProperties = {
@@ -143,9 +139,23 @@ export const fs_mkdirall = async (path: string, opts: NodeOptions) => {
} }
export const fs_get_node = async (path: string) => { export const fs_get_node = async (path: string) => {
return await fs_check_response( const resp = await fs_check_response(
await fetch(fs_path_url(path) + "?stat") await fetch(fs_path_url(path) + "?stat")
) as FSPath ) as FSPath
const fsp = new FSPath()
fsp.path = new Array(resp.path.length)
resp.path.forEach((node, index) => {
fsp.path[index] = Object.assign(new FSNode(), node)
})
fsp.base_index = resp.base_index
fsp.children = new Array(resp.children.length)
resp.children.forEach((node, index) => {
fsp.children[index] = Object.assign(new FSNode(), node)
})
fsp.permissions = resp.permissions
fsp.context = resp.context
return fsp
} }
// Updates a node's parameters. Available options are: // Updates a node's parameters. Available options are:
@@ -171,9 +181,10 @@ export const fs_update = async (path: string, opts: NodeOptions) => {
} }
} }
return await fs_check_response( const resp = await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form }) await fetch(fs_path_url(path), { method: "POST", body: form })
) as FSNode )
return Object.assign(new FSNode(), resp)
} }
export const fs_rename = async (old_path: string, new_path: string) => { export const fs_rename = async (old_path: string, new_path: string) => {
@@ -181,9 +192,10 @@ export const fs_rename = async (old_path: string, new_path: string) => {
form.append("action", "rename") form.append("action", "rename")
form.append("target", new_path) form.append("target", new_path)
return await fs_check_response( const resp = await fs_check_response(
await fetch(fs_path_url(old_path), { method: "POST", body: form }) await fetch(fs_path_url(old_path), { method: "POST", body: form })
) as FSNode )
return Object.assign(new FSNode(), resp)
} }
export const fs_delete = async (path: string) => { export const fs_delete = async (path: string) => {
@@ -334,7 +346,7 @@ export const fs_node_type = (node: FSNode) => {
export const fs_node_icon = (node: FSNode, width = 64, height = 64) => { export const fs_node_icon = (node: FSNode, width = 64, height = 64) => {
if (node.type === "dir") { if (node.type === "dir") {
// Folders with an ID are publically shared, use the shared folder icon // Folders with an ID are publically shared, use the shared folder icon
if (node_is_shared(node)) { if (node.is_shared()) {
return "/res/img/mime/folder-remote.png" return "/res/img/mime/folder-remote.png"
} else { } else {
return "/res/img/mime/folder.png" return "/res/img/mime/folder.png"
@@ -372,7 +384,7 @@ export const fs_share_path = (path: FSNode[]): string => {
// Find the last node in the path that has a public ID // Find the last node in the path that has a public ID
for (let i = path.length - 1; i >= 0; i--) { for (let i = path.length - 1; i >= 0; i--) {
if (node_is_shared(path[i])) { if (path[i].is_shared()) {
bucket_idx = i bucket_idx = i
break break
} }

View File

@@ -20,21 +20,21 @@ let {
padding = false, padding = false,
visible = $bindable(false), visible = $bindable(false),
style = "", style = "",
title_slot, header,
children children
}: { }: {
// Form can be used to turn the modal into a save dialog. Enter the ID of a // Form can be used to turn the modal into a save dialog. Enter the ID of a
// form inside the modal and the modal will provide a submit button for that // form inside the modal and the modal will provide a submit button for that
// form // form
form: string, form?: string,
title: string, title?: string,
width: string, width?: string,
height: string, height?: string,
padding: boolean, padding?: boolean,
visible: boolean, visible?: boolean,
style: string, style?: string,
title_slot: Snippet, header?: Snippet,
children: Snippet, children?: Snippet,
} = $props(); } = $props();
const load_bg = background => { const load_bg = background => {
@@ -91,7 +91,7 @@ const keydown = (e: KeyboardEvent) => {
onkeydown={keydown} onkeydown={keydown}
> >
<div class="header"> <div class="header">
{#if title_slot}{@render title_slot()}{:else} {#if header}{@render header()}{:else}
{#if form !== ""} {#if form !== ""}
<Button round click={hide} icon="close"/> <Button round click={hide} icon="close"/>
<span class="title">{title}</span> <span class="title">{title}</span>

View File

@@ -1,22 +1,50 @@
<script lang="ts"> <script lang="ts">
import { bookmark_del, bookmarks_store } from "lib/Bookmarks"; import { bookmark_del, bookmarks_store } from "lib/Bookmarks";
import { fs_encode_path } from "lib/FilesystemAPI"; import { fs_encode_path } from "lib/FilesystemAPI.svelte";
import { highlight_current_page } from "lib/HighlightCurrentPage"; import { highlight_current_page } from "lib/HighlightCurrentPage";
let editing = $state(false)
const toggle_edit = () => {
editing = !editing
}
</script> </script>
<div class="title">
<div class="hide_small">Bookmarks</div>
<button onclick={() => toggle_edit()} class:button_highlight={editing}>
<i class="icon">edit</i>
</button>
</div>
{#each $bookmarks_store as bookmark} {#each $bookmarks_store as bookmark}
<div class="row"> <div class="row">
<a class="button" href="/d{fs_encode_path(bookmark.path)}" use:highlight_current_page> <a class="button" href="/d{fs_encode_path(bookmark.path)}" use:highlight_current_page>
<i class="icon">{bookmark.icon}</i> <i class="icon">{bookmark.icon}</i>
<span class="hide_small">{bookmark.label}</span> <span class="hide_small">{bookmark.label}</span>
</a> </a>
<button onclick={() => bookmark_del(bookmark.id)}> {#if editing}
<i class="icon">delete</i> <button onclick={() => bookmark_del(bookmark.id)}>
</button> <i class="icon">delete</i>
</button>
{/if}
</div> </div>
{/each} {/each}
<style> <style>
.title {
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1px solid var(--separator);
}
.title > div {
flex: 1 1 auto;
text-align: center;
}
.title > button {
flex: 0 0 auto;
}
.row { .row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@@ -6,7 +6,7 @@ import { formatDataVolume } from "util/Formatting";
import Router from "wrap/Router.svelte"; import Router from "wrap/Router.svelte";
import Bookmarks from "./Bookmarks.svelte"; import Bookmarks from "./Bookmarks.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fs_get_node } from "lib/FilesystemAPI"; import { fs_get_node } from "lib/FilesystemAPI.svelte";
import { css_from_path } from "filesystem/edit_window/Branding"; import { css_from_path } from "filesystem/edit_window/Branding";
import { loading_run, loading_store } from "lib/Loading"; import { loading_run, loading_store } from "lib/Loading";
import Spinner from "util/Spinner.svelte"; import Spinner from "util/Spinner.svelte";
@@ -25,82 +25,84 @@ onMount(async () => {
</script> </script>
<div class="nav_container"> <div class="nav_container">
<nav class="nav"> <div class="scroll_container">
<a class="button" href="/" use:highlight_current_page> <nav class="nav">
<i class="icon">home</i> <a class="button" href="/" use:highlight_current_page>
<span class="hide_small">Home</span> <i class="icon">home</i>
</a> <span class="hide_small">Home</span>
</a>
{#if $user.username !== undefined && $user.username !== ""} {#if $user.username !== undefined && $user.username !== ""}
<div class="separator hide_small"></div> <div class="separator hide_small"></div>
<div class="username hide_small"> <div class="username hide_small">
{$user.username} {$user.username}
</div> </div>
<div class="separator"></div>
<div class="stats_table hide_small">
<div>Subscription</div>
<div>{$user.subscription.name}</div>
{#if $user.subscription.type === "prepaid"}
<div>Credit</div>
<div><Euro amount={$user.balance_micro_eur}/></div>
{/if}
<div>Storage used</div>
<div>{formatDataVolume($user.filesystem_storage_used, 3)}</div>
<div>Transfer used</div>
<div>{formatDataVolume($user.monthly_transfer_used, 3)}</div>
</div>
<div class="separator hide_small"></div>
<a class="button" href="/d/me" use:highlight_current_page>
<i class="icon">folder</i>
<span class="hide_small">Filesystem</span>
</a>
<a class="button" href="/user" use:highlight_current_page>
<i class="icon">dashboard</i>
<span class="hide_small">Dashboard</span>
</a>
{#if $user.is_admin}
<a class="button" href="/admin" use:highlight_current_page>
<i class="icon">admin_panel_settings</i>
<span class="hide_small">Admin Panel</span>
</a>
{/if}
<button class="button" onclick={()=> logout_user("/")}>
<i class="icon">logout</i>
<span class="hide_small">Log out</span>
</button>
{:else}
<a class="button" href="/login" use:highlight_current_page>
<i class="icon">login</i>
<span class="hide_small">Login</span>
</a>
<a class="button" href="/register" use:highlight_current_page>
<i class="icon">how_to_reg</i>
<span class="hide_small">Register</span>
</a>
{/if}
<div class="separator"></div> <div class="separator"></div>
<div class="stats_table hide_small"> <a class="button" href="/speedtest" use:highlight_current_page>
<div>Subscription</div> <i class="icon">speed</i>
<div>{$user.subscription.name}</div> <span class="hide_small">Speedtest</span>
{#if $user.subscription.type === "prepaid"}
<div>Credit</div>
<div><Euro amount={$user.balance_micro_eur}/></div>
{/if}
<div>Storage used</div>
<div>{formatDataVolume($user.filesystem_storage_used, 3)}</div>
<div>Transfer used</div>
<div>{formatDataVolume($user.monthly_transfer_used, 3)}</div>
</div>
<div class="separator hide_small"></div>
<a class="button" href="/d/me" use:highlight_current_page>
<i class="icon">folder</i>
<span class="hide_small">Filesystem</span>
</a> </a>
<a class="button" href="/user" use:highlight_current_page> <a class="button" href="/appearance" use:highlight_current_page>
<i class="icon">dashboard</i> <i class="icon">palette</i>
<span class="hide_small">Dashboard</span> <span class="hide_small">Themes</span>
</a> </a>
{#if $user.is_admin}
<a class="button" href="/admin" use:highlight_current_page>
<i class="icon">admin_panel_settings</i>
<span class="hide_small">Admin Panel</span>
</a>
{/if}
<button class="button" onclick={()=> logout_user("/")}>
<i class="icon">logout</i>
<span class="hide_small">Log out</span>
</button>
{:else}
<a class="button" href="/login" use:highlight_current_page>
<i class="icon">login</i>
<span class="hide_small">Login</span>
</a>
<a class="button" href="/register" use:highlight_current_page>
<i class="icon">how_to_reg</i>
<span class="hide_small">Register</span>
</a>
{/if}
<div class="separator"></div> <div class="separator"></div>
<a class="button" href="/speedtest" use:highlight_current_page> <Bookmarks/>
<i class="icon">speed</i> </nav>
<span class="hide_small">Speedtest</span> </div>
</a>
<a class="button" href="/appearance" use:highlight_current_page>
<i class="icon">palette</i>
<span class="hide_small">Themes</span>
</a>
<div class="separator"></div>
<Bookmarks/>
</nav>
</div> </div>
<div class="page"> <div class="page">
@@ -133,13 +135,17 @@ onMount(async () => {
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
} }
.scroll_container {
position: sticky;
top: 0;
max-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
}
.nav { .nav {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 15em; max-width: 15em;
position: sticky;
top: 0;
} }
.nav > .button { .nav > .button {
background: none; background: none;
@@ -165,7 +171,7 @@ onMount(async () => {
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;
gap: 0.2em 1em; gap: 0.2em 1em;
margin: 3px; margin: 5px;
} }
@media(max-width: 1000px) { @media(max-width: 1000px) {