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

@@ -1,8 +1,11 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { fs_encode_path, node_is_shared } from "lib/FilesystemAPI";
import type { FSNavigator } from "./FSNavigator";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
</script>
<div class="breadcrumbs">
@@ -10,7 +13,7 @@ export let nav: FSNavigator
<a
href={"/d"+fs_encode_path(node.path)}
class="breadcrumb button flat"
on:click|preventDefault={() => {nav.navigate(node.path, true)}}
onclick={preventDefault(() => {nav.navigate(node.path, true)})}
>
{#if node.abuse_type !== undefined}
<i class="icon small">block</i>

View File

@@ -1,31 +1,34 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import Chart from "util/Chart.svelte";
import { formatDataVolume, formatDate, formatThousands } from "util/Formatting";
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 { color_by_name } from "util/Util.svelte";
import { color_by_name } from "util/Util";
import { tick } from "svelte";
import CopyButton from "layout/CopyButton.svelte";
import type { FSNavigator } from "./FSNavigator";
export let nav: FSNavigator
export let visible = false
let {
nav,
visible = $bindable(false)
}: {
nav: FSNavigator;
visible?: boolean;
} = $props();
export const toggle = () => visible = !visible
$: visibility_change(visible)
const visibility_change = visible => {
if (visible) {
update_chart(nav.base, 0, 0)
}
}
$: direct_url = $nav.base.path ? window.location.origin+fs_path_url($nav.base.path) : ""
$: share_url = fs_share_url($nav.path)
$: direct_share_url = fs_share_hotlink_url($nav.path)
let chart
let chart_timespan = 0
let chart_interval = 0
let chart: Chart = $state()
let chart_timespan = $state(0)
let chart_interval = $state(0)
let chart_timespans = [
{label: "Day (1m)", span: 1440, interval: 1},
{label: "Week (1h)", span: 10080, interval: 60},
@@ -36,10 +39,9 @@ let chart_timespans = [
{label: "Five Years (1d)", span: 2628000, interval: 1440},
]
let total_downloads = 0
let total_transfer = 0
let total_downloads = $state(0)
let total_transfer = $state(0)
$: update_chart($nav.base, chart_timespan, chart_interval)
let update_chart = async (base: FSNode, timespan: number, interval: number) => {
if (chart === undefined) {
// Wait for the chart element to render, if it's not rendered already
@@ -141,6 +143,15 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
console.error("Failed to get time series data:", err)
}
}
run(() => {
visibility_change(visible)
});
let direct_url = $derived($nav.base.path ? window.location.origin+fs_path_url($nav.base.path) : "")
let share_url = $derived(fs_share_url($nav.path))
let direct_share_url = $derived(fs_share_hotlink_url($nav.path))
run(() => {
update_chart($nav.base, chart_timespan, chart_interval)
});
</script>
<Modal bind:visible={visible} title="Details" width={($nav.base.type === "file" ? 1000 : 750) + "px"}>
@@ -231,7 +242,7 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
<div class="button_bar">
{#each chart_timespans as ts}
<button
on:click={() => update_chart($nav.base, ts.span, ts.interval)}
onclick={() => update_chart($nav.base, ts.span, ts.interval)}
class:button_highlight={chart_timespan == ts.span}>
{ts.label}
</button>

View File

@@ -4,13 +4,15 @@ import { formatDataVolume, formatThousands } from "util/Formatting"
import { fs_path_url } from "lib/FilesystemAPI";
import type { FSNavigator } from "./FSNavigator";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
let loading = true
let downloads = 0
let transfer_used = 0
let loading = $state(true)
let downloads = $state(0)
let transfer_used = $state(0)
let socket = null
let error_msg = ""
let error_msg = $state("")
let connected_to = ""
@@ -22,9 +24,9 @@ onMount(() => {
}
})
let total_directories = 0
let total_files = 0
let total_file_size = 0
let total_directories = $state(0)
let total_files = $state(0)
let total_file_size = $state(0)
const update_base = async () => {
if (!nav.initialized) {

View File

@@ -12,14 +12,14 @@ import { css_from_path } from "filesystem/edit_window/Branding";
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";
import { current_page_store } from "wrap/RouterStore";
let file_preview: FilePreview
let toolbar: Toolbar
let upload_widget: FSUploadWidget
let details_visible = false
let edit_window: EditWindow
let edit_visible = false
let file_preview: FilePreview = $state()
let toolbar: Toolbar = $state()
let upload_widget: FSUploadWidget = $state()
let details_visible = $state(false)
let edit_window: EditWindow = $state()
let edit_visible = $state(false)
const nav = new FSNavigator(true)
const nav = $state(new FSNavigator(true))
onMount(() => {
if ((window as any).intial_node !== undefined) {
@@ -134,7 +134,7 @@ const keydown = (e: KeyboardEvent) => {
};
</script>
<svelte:window on:keydown={keydown} />
<svelte:window onkeydown={keydown} />
<div class="filesystem">
<Breadcrumbs nav={nav}/>

View File

@@ -6,23 +6,31 @@ import { formatDataVolume } from "util/Formatting";
import { user } from "lib/UserStore";
import Dialog from "layout/Dialog.svelte";
let button: HTMLButtonElement
let dialog: Dialog
let button: HTMLButtonElement = $state()
let dialog: Dialog = $state()
export let no_login_label = "Pixeldrain"
let {
no_login_label = "Pixeldrain",
hide_name = true,
hide_logo = false,
style = "",
embedded = false
}: {
no_login_label?: string;
// Hide the label if the screen is smaller than 800px
hide_name?: boolean;
hide_logo?: boolean;
style?: string;
embedded?: boolean;
} = $props();
// Hide the label if the screen is smaller than 800px
export let hide_name = true
export let hide_logo = false
export let style = ""
export let embedded = false
$: target = embedded ? "_blank" : "_self"
let target = $derived(embedded ? "_blank" : "_self")
const open = () => dialog.open(button.getBoundingClientRect())
</script>
<div class="wrapper">
<button bind:this={button} on:click={open} class="button round" title="Menu" style={style}>
<button bind:this={button} onclick={open} class="button round" title="Menu" style={style}>
{#if !hide_logo}
<PixeldrainLogo style="height: 1.6em; width: 1.6em;"/>
{/if}

View File

@@ -1,22 +1,24 @@
<script lang="ts">
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 { copy_text } from "util/Util.svelte";
import { copy_text } from "util/Util";
import CopyButton from "layout/CopyButton.svelte";
import Dialog from "layout/Dialog.svelte";
import { fade } from "svelte/transition";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
let path: FSNode[]
let base: FSNode
let toast = ""
let share_url = ""
let direct_share_url = ""
let is_parent = false
let parent_node: FSNode
let base: FSNode = $state()
let toast = $state("")
let share_url = $state("")
let direct_share_url = $state("")
let is_parent = $state(false)
let parent_node: FSNode = $state()
let dialog: Dialog
let dialog: Dialog = $state()
export const open = async (e: MouseEvent, p: FSNode[]) => {
path = p
base = path[path.length-1]
@@ -113,7 +115,7 @@ const share = async () => {
<img src={fs_node_icon(parent_node, 64, 64)} class="node_icon" alt="icon"/>
{parent_node.name}
<br/>
<button on:click={async e => {await make_public(); await share()}} style="display: inline;">
<button onclick={async e => {await make_public(); await share()}} style="display: inline;">
Only share
<img src={fs_node_icon(base, 64, 64)} class="node_icon" alt="icon"/>
{base.name}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { copy_text } from "util/Util.svelte";
import { copy_text } from "util/Util";
import FileStats from "./FileStats.svelte";
import type { FSNavigator } from "./FSNavigator";
import EditWindow from "./edit_window/EditWindow.svelte";
@@ -10,12 +10,20 @@ import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bo
let dispatch = createEventDispatcher()
export let nav: FSNavigator
export let details_visible = false
export let edit_window: EditWindow
export let edit_visible = false
let share_dialog: ShareDialog
let link_copied = false
let {
nav = $bindable(),
details_visible = $bindable(false),
edit_window,
edit_visible = $bindable(false)
}: {
nav: FSNavigator;
details_visible?: boolean;
edit_window: EditWindow;
edit_visible?: boolean;
} = $props();
let share_dialog: ShareDialog = $state()
let link_copied = $state(false)
export const copy_link = () => {
const share_url = fs_share_url($nav.path)
if (share_url === "") {
@@ -34,36 +42,36 @@ export const copy_link = () => {
<FileStats nav={nav}/>
<div class="button_row">
<button on:click={() => {nav.open_sibling(-1)}}>
<button onclick={() => {nav.open_sibling(-1)}}>
<i class="icon">skip_previous</i>
</button>
<button on:click={() => {nav.shuffle = !nav.shuffle}} class:button_highlight={nav.shuffle}>
<button onclick={() => {nav.shuffle = !nav.shuffle}} class:button_highlight={nav.shuffle}>
<i class="icon">shuffle</i>
</button>
<button on:click={() => {nav.open_sibling(1)}}>
<button onclick={() => {nav.open_sibling(1)}}>
<i class="icon">skip_next</i>
</button>
</div>
<button on:click={() => dispatch("download")}>
<button onclick={() => dispatch("download")}>
<i class="icon">save</i>
<span>Download</span>
</button>
{#if is_bookmark($bookmarks_store, $nav.base.id)}
<button on:click={() => bookmark_del($nav.base.id)}>
<button onclick={() => bookmark_del($nav.base.id)}>
<i class="icon">bookmark_remove</i>
<span>Bookmark</span>
</button>
{:else}
<button on:click={() => bookmark_add($nav.base)}>
<button onclick={() => bookmark_add($nav.base)}>
<i class="icon">bookmark_add</i>
<span>Bookmark</span>
</button>
{/if}
{#if path_is_shared($nav.path)}
<button on:click={copy_link} class:button_highlight={link_copied}>
<button onclick={copy_link} class:button_highlight={link_copied}>
<i class="icon">content_copy</i>
<span><u>C</u>opy link</span>
</button>
@@ -71,19 +79,19 @@ export const copy_link = () => {
<!-- Share button is enabled when: The browser has a sharing API, or the user can edit the file (to enable sharing)-->
{#if navigator.share !== undefined || $nav.permissions.write === true}
<button on:click={(e) => share_dialog.open(e, nav.path)}>
<button onclick={(e) => share_dialog.open(e, nav.path)}>
<i class="icon">share</i>
<span>Share</span>
</button>
{/if}
<button on:click={() => details_visible = !details_visible} class:button_highlight={details_visible}>
<button onclick={() => details_visible = !details_visible} class:button_highlight={details_visible}>
<i class="icon">help</i>
<span>Deta<u>i</u>ls</span>
</button>
{#if $nav.base.id !== "me" && $nav.permissions.write === true}
<button on:click={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
<button onclick={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
<i class="icon">edit</i>
<span><u>E</u>dit</span>
</button>

View File

@@ -3,10 +3,14 @@ import Button from "layout/Button.svelte";
import type { FSPermissions, NodeOptions } from "lib/FilesystemAPI";
import PermissionButton from "./PermissionButton.svelte";
export let options: NodeOptions
let {
options = $bindable()
}: {
options: NodeOptions;
} = $props();
let new_user_id = ""
let new_user_perms = <FSPermissions>{read: true}
let new_user_id = $state("")
let new_user_perms = $state(<FSPermissions>{read: true})
const add_user = (e: SubmitEvent) => {
e.preventDefault()
if (options.user_permissions === undefined) {
@@ -19,8 +23,8 @@ const del_user = (id: string) => {
options.user_permissions = options.user_permissions
}
let new_password = ""
let new_password_perms = <FSPermissions>{read: true}
let new_password = $state("")
let new_password_perms = $state(<FSPermissions>{read: true})
const add_password = (e: SubmitEvent) => {
e.preventDefault()
if (options.password_permissions === undefined) {
@@ -64,7 +68,7 @@ const del_password = (pass: string) => {
not receive an e-mail invite. Giving write access to a user without giving
read access as well does not actually allow them to write anything.
</p>
<form on:submit={add_user} class="row">
<form onsubmit={add_user} class="row">
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
<Button type="submit" icon="add" label="Add"/>
<div class="perms">
@@ -94,7 +98,7 @@ const del_password = (pass: string) => {
<p>
<b>This feature is not implemented currently!</b>
</p>
<form on:submit={add_password} class="row">
<form onsubmit={add_password} class="row">
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1">
<Button type="submit" icon="add" label="Add"/>
<div class="perms">

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import { createEventDispatcher } from "svelte";
import ThemePresets from "./ThemePresets.svelte";
import { fs_update, fs_node_type, type FSNode, type NodeOptions, node_is_shared, type FSPermissions } from "lib/FilesystemAPI";
@@ -7,11 +8,16 @@ import HelpButton from "layout/HelpButton.svelte";
import FilePicker from "filesystem/filemanager/FilePicker.svelte";
let dispatch = createEventDispatcher()
export let file: FSNode
export let options: NodeOptions
export let enabled: boolean
let {
file = $bindable(),
options = $bindable(),
enabled = $bindable()
}: {
file: FSNode;
options: NodeOptions;
enabled: boolean;
} = $props();
$: update_colors(options)
const update_colors = (options: NodeOptions) => {
if (enabled) {
options.branding_enabled = "true"
@@ -21,7 +27,7 @@ const update_colors = (options: NodeOptions) => {
}
}
let picker: FilePicker
let picker: FilePicker = $state()
let picking = ""
const pick_image = (type: string) => {
picking = type
@@ -61,7 +67,10 @@ const handle_picker = async (e: CustomEvent<FSNode[]>) => {
}
}
let highlight_info = false
let highlight_info = $state(false)
run(() => {
update_colors(options)
});
</script>
<fieldset>
@@ -127,7 +136,7 @@ let highlight_info = false
working. Recommended dimensions for the header image are 1000x90 px.
</p>
<div>Header image ID</div>
<button on:click={() => pick_image("brand_header_image")}>
<button onclick={() => pick_image("brand_header_image")}>
<i class="icon">folder_open</i>
Pick
</button>
@@ -135,7 +144,7 @@ let highlight_info = false
<div>Header image link</div>
<input class="span2" type="text" bind:value={options.brand_header_link}/>
<div>Background image ID</div>
<button on:click={() => pick_image("brand_background_image")}>
<button onclick={() => pick_image("brand_background_image")}>
<i class="icon">folder_open</i>
Pick
</button>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { fs_rename, fs_update, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
import Modal from "util/Modal.svelte";
import BrandingOptions from "./BrandingOptions.svelte";
@@ -9,13 +10,18 @@ import AccessControl from "./AccessControl.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
let file: FSNode = {} as FSNode
let options: NodeOptions = {} as NodeOptions
let file: FSNode = $state({} as FSNode)
let options: NodeOptions = $state({} as NodeOptions)
let custom_css = ""
let custom_css = $state("")
export let visible: boolean
let {
nav,
visible = $bindable()
}: {
nav: FSNavigator;
visible: boolean;
} = $props();
// Open the edit window. Argument 1 is the file to edit, 2 is whether the file
// should be opened after the user finishes editing and 3 is the default tab
@@ -54,11 +60,11 @@ export const edit = (f: FSNode, oae = false, open_tab = "") => {
visible = true
}
let tab = "file"
let open_after_edit = false
let tab = $state("file")
let open_after_edit = $state(false)
let new_name = ""
let branding_enabled = false
let new_name = $state("")
let branding_enabled = $state(false)
const save = async (keep_editing = false) => {
console.debug("Saving file", file.path)
@@ -106,27 +112,27 @@ const save = async (keep_editing = false) => {
<Modal bind:visible={visible} title="Edit {file.name}" width="800px" form="edit_form" style="color: var(--body_text_color); {custom_css}">
<div class="tab_bar">
<button class:button_highlight={tab === "file"} on:click={() => tab = "file"}>
<button class:button_highlight={tab === "file"} onclick={() => tab = "file"}>
<i class="icon">edit</i>
Properties
</button>
<button class:button_highlight={tab === "share"} on:click={() => tab = "share"}>
<button class:button_highlight={tab === "share"} onclick={() => tab = "share"}>
<i class="icon">share</i>
Sharing
</button>
{#if $nav.permissions.owner}
<button class:button_highlight={tab === "access"} on:click={() => tab = "access"}>
<button class:button_highlight={tab === "access"} onclick={() => tab = "access"}>
<i class="icon">key</i>
Access control
</button>
{/if}
<button class:button_highlight={tab === "branding"} on:click={() => tab = "branding"}>
<button class:button_highlight={tab === "branding"} onclick={() => tab = "branding"}>
<i class="icon">palette</i>
Branding
</button>
</div>
<form id="edit_form" on:submit|preventDefault={() => save(false)}></form>
<form id="edit_form" onsubmit={preventDefault(() => save(false))}></form>
<div class="tab_content">
{#if tab === "file"}
@@ -138,7 +144,10 @@ const save = async (keep_editing = false) => {
bind:open_after_edit
/>
{:else if tab === "share"}
<SharingOptions bind:file bind:options on:save={() => save(true)} />
<SharingOptions
bind:file
bind:options
/>
{:else if tab === "access"}
<AccessControl bind:options />
{:else if tab === "branding"}

View File

@@ -5,13 +5,21 @@ import PathLink from "filesystem/util/PathLink.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
export let file: FSNode = {} as FSNode
export let new_name: string
export let visible: boolean
export let open_after_edit: boolean
let {
nav,
file = $bindable({} as FSNode),
new_name = $bindable(),
visible = $bindable(),
open_after_edit = $bindable(false)
}: {
nav: FSNavigator;
file?: FSNode;
new_name: string;
visible: boolean;
open_after_edit: boolean;
} = $props();
$: is_root_dir = file.path === "/"+file.id
let is_root_dir = $derived(file.path === "/"+file.id)
const delete_file = async (e: MouseEvent) => {
e.preventDefault()

View File

@@ -2,9 +2,15 @@
import ToggleButton from "layout/ToggleButton.svelte";
import type { FSPermissions } from "lib/FilesystemAPI";
export let permissions = <FSPermissions>{}
let {
permissions = $bindable()
}: {
permissions: FSPermissions
} = $props();
</script>
<ToggleButton group_first bind:on={permissions.read}>Read</ToggleButton>
<ToggleButton group_middle bind:on={permissions.write}>Write</ToggleButton>
<ToggleButton group_last bind:on={permissions.delete}>Delete</ToggleButton>
{#if permissions !== undefined}
<ToggleButton group_first bind:on={permissions.read}>Read</ToggleButton>
<ToggleButton group_middle bind:on={permissions.write}>Write</ToggleButton>
<ToggleButton group_last bind:on={permissions.delete}>Delete</ToggleButton>
{/if}

View File

@@ -1,18 +1,22 @@
<script lang="ts">
import { domain_url } from "util/Util.svelte";
import { run } from 'svelte/legacy';
import { domain_url } from "util/Util";
import CopyButton from "layout/CopyButton.svelte";
import { formatDate } from "util/Formatting";
import { node_is_shared, type FSNode, type NodeOptions } from "lib/FilesystemAPI";
import AccessControl from "./AccessControl.svelte";
export let file: FSNode = {} as FSNode
export let options: NodeOptions
let {
file = $bindable(),
options = $bindable(),
}: {
file?: FSNode;
options: NodeOptions;
} = $props();
let embed_html: string
let preview_area: HTMLDivElement
let embed_html: string = $state()
let preview_area: HTMLDivElement = $state()
$: share_link = window.location.protocol+"//"+window.location.host+"/d/"+file.id
$: embed_iframe(file, options)
const embed_iframe = (file: FSNode, options: NodeOptions) => {
if (!node_is_shared(file)) {
example = false
@@ -28,7 +32,7 @@ const embed_iframe = (file: FSNode, options: NodeOptions) => {
`></iframe>`
}
let example = false
let example = $state(false)
const toggle_example = () => {
if (node_is_shared(file)) {
example = !example
@@ -40,6 +44,10 @@ const toggle_example = () => {
}
}
let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id)
run(() => {
embed_iframe(file, options)
});
</script>
<fieldset>
@@ -78,7 +86,7 @@ const toggle_example = () => {
<textarea bind:value={embed_html} style="width: 100%; height: 4em;"></textarea>
<br/>
<CopyButton text={embed_html}>Copy HTML</CopyButton>
<button on:click={toggle_example} class:button_highlight={example} disabled={!node_is_shared(file)}>
<button onclick={toggle_example} class:button_highlight={example} disabled={!node_is_shared(file)}>
<i class="icon">visibility</i> Show example
</button>
</div>

View File

@@ -1,9 +1,13 @@
<script lang="ts">
import type { FSNodeProperties } from "lib/FilesystemAPI";
export let properties: FSNodeProperties = {} as FSNodeProperties
let {
properties = $bindable({} as FSNodeProperties)
}: {
properties?: FSNodeProperties;
} = $props();
let current_theme = -1
let current_theme = $state(-1)
const set_theme = (index: number) => {
current_theme = index
@@ -71,7 +75,7 @@ const themes = [
</script>
{#each themes as theme, index (theme.name)}
<button class:button_highlight={current_theme === index} on:click={() => {set_theme(index)}}>
<button class:button_highlight={current_theme === index} onclick={() => {set_theme(index)}}>
{theme.name}
</button>
{/each}

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>

View File

@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
export type UploadJob = {
task_id: number,
file: File,
@@ -14,9 +14,11 @@ import { tick } from "svelte";
import UploadProgress from "./UploadProgress.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
let file_input_field: HTMLInputElement
let file_input_field: HTMLInputElement = $state()
let file_input_change = (e: Event) => {
// Start uploading the files async
upload_files((e.target as HTMLInputElement).files)
@@ -28,8 +30,8 @@ export const pick_files = () => {
file_input_field.click()
}
let visible = false
let upload_queue: UploadJob[] = [];
let visible = $state(false)
let upload_queue: UploadJob[] = $state([]);
let task_id_counter = 0
export const upload_files = async (files: File[]|FileList) => {
@@ -76,8 +78,8 @@ export const upload_file = async (file: File) => {
// each upload progress bar will have bound itself to its array item
upload_queue = upload_queue
if (active_uploads === 0 && state !== "uploading") {
state = "uploading"
if (active_uploads === 0 && status !== "uploading") {
status = "uploading"
visible = true
await tick()
await start_upload()
@@ -85,7 +87,7 @@ export const upload_file = async (file: File) => {
}
let active_uploads = 0
let state = "idle"
let status = $state("idle")
const start_upload = async () => {
active_uploads = 0
@@ -115,7 +117,7 @@ const start_upload = async () => {
}
if (active_uploads === 0) {
state = "finished"
status = "finished"
nav.reload()
// Empty the queue to free any references to lingering components
@@ -123,12 +125,12 @@ const start_upload = async () => {
// In ten seconds we close the popup
setTimeout(() => {
if (state === "finished") {
if (status === "finished") {
visible = false
}
}, 10000)
} else {
state = "uploading"
status = "uploading"
}
}
@@ -139,7 +141,7 @@ const finish_upload = () => {
}
const leave_confirmation = (e: BeforeUnloadEvent) => {
if (state === "uploading") {
if (status === "uploading") {
e.preventDefault()
return "If you close this page your files will stop uploading. Do you want to continue?"
} else {
@@ -148,22 +150,22 @@ const leave_confirmation = (e: BeforeUnloadEvent) => {
}
</script>
<svelte:window on:beforeunload={leave_confirmation} />
<svelte:window onbeforeunload={leave_confirmation} />
<input
bind:this={file_input_field}
on:change={file_input_change}
onchange={file_input_change}
class="upload_input" type="file" name="file" multiple
/>
{#if visible}
<div class="upload_widget">
<div class="header">
{#if state === "idle"}
{#if status === "idle"}
Waiting for files
{:else if state === "uploading"}
{:else if status === "uploading"}
Uploading files...
{:else if state === "finished"}
{:else if status === "finished"}
Done
{/if}
</div>

View File

@@ -6,11 +6,19 @@ import Button from "layout/Button.svelte"
import type { UploadJob } from "./FSUploadWidget.svelte";
let dispatch = createEventDispatcher()
export let job: UploadJob
export let total = 0
export let loaded = 0
let error_code = ""
let error_message = ""
let {
job = $bindable(),
total = $bindable(0),
loaded = $bindable(0)
}: {
job: UploadJob;
total?: number;
loaded?: number;
} = $props();
let error_code = $state("")
let error_message = $state("")
let xhr: XMLHttpRequest = null
export const start = () => {

View File

@@ -1,10 +1,18 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import type { FSNavigator } from "filesystem/FSNavigator";
export let nav: FSNavigator
export let path = ""
let {
nav,
path = "",
children
}: {
nav: FSNavigator;
path?: string;
children?: import('svelte').Snippet;
} = $props();
</script>
<a href={"/d"+path} on:click|preventDefault={() => {nav.navigate(path, true)}}>
<slot></slot>
<a href={"/d"+path} onclick={preventDefault(() => {nav.navigate(path, true)})}>
{@render children?.()}
</a>

View File

@@ -1,14 +1,19 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { onMount } from 'svelte'
import { fs_path_url, fs_encode_path, fs_node_icon } from "lib/FilesystemAPI"
import TextBlock from "layout/TextBlock.svelte"
import type { FSNavigator } from 'filesystem/FSNavigator';
export let nav: FSNavigator
let player: HTMLAudioElement
let playing = false
let { nav, children }: {
nav: FSNavigator;
children?: import('svelte').Snippet;
} = $props();
let player: HTMLAudioElement = $state()
let playing = $state(false)
let media_session = false
let siblings = []
let siblings = $state([])
export const toggle_playback = () => playing ? player.pause() : player.play()
export const toggle_mute = () => player.muted = !player.muted
@@ -47,7 +52,7 @@ onMount(() => {
})
</script>
<slot></slot>
{@render children?.()}
<TextBlock width="1000px">
<audio
@@ -56,30 +61,30 @@ onMount(() => {
src={fs_path_url($nav.base.path)}
autoplay
controls
on:pause={() => playing = false }
on:play={() => playing = true }
on:ended={() => nav.open_sibling(1) }>
onpause={() => playing = false}
onplay={() => playing = true}
onended={() => nav.open_sibling(1)}>
<track kind="captions"/>
</audio>
<div style="text-align: center;">
<button on:click={() => nav.open_sibling(-1) }><i class="icon">skip_previous</i></button>
<button on:click={() => seek(-10) }><i class="icon">replay_10</i></button>
<button on:click={toggle_playback}>
<button onclick={() => nav.open_sibling(-1)}><i class="icon">skip_previous</i></button>
<button onclick={() => seek(-10)}><i class="icon">replay_10</i></button>
<button onclick={toggle_playback}>
{#if playing}
<i class="icon">pause</i>
{:else}
<i class="icon">play_arrow</i>
{/if}
</button>
<button on:click={() => seek(10) }><i class="icon">forward_10</i></button>
<button on:click={() => nav.open_sibling(1) }><i class="icon">skip_next</i></button>
<button onclick={() => seek(10)}><i class="icon">forward_10</i></button>
<button onclick={() => nav.open_sibling(1)}><i class="icon">skip_next</i></button>
</div>
<h2>Tracklist</h2>
{#each siblings as sibling (sibling.path)}
<a
href={"/d"+fs_encode_path(sibling.path)}
on:click|preventDefault={() => nav.navigate(sibling.path, true)}
onclick={preventDefault(() => nav.navigate(sibling.path, true))}
class="node"
>
{#if sibling.path === $nav.base.path}

View File

@@ -1,10 +1,12 @@
<script lang="ts">
export let path = []
import type { FSNode } from 'lib/FilesystemAPI';
import { run } from 'svelte/legacy';
let image_uri: string
let image_link: string
$: update_links(path)
const update_links = (path) => {
let { path = [] }: {path: FSNode[]} = $props();
let image_uri: string = $state()
let image_link: string = $state()
const update_links = (path: FSNode[]) => {
image_uri = null
image_link = null
for (let node of path) {
@@ -18,6 +20,9 @@ const update_links = (path) => {
}
}
}
run(() => {
update_links(path)
});
</script>
{#if image_uri}

View File

@@ -8,10 +8,13 @@ import type { FSNavigator } from "filesystem/FSNavigator";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
let { nav, children }: {
nav: FSNavigator;
children?: import('svelte').Snippet;
} = $props();
</script>
<slot></slot>
{@render children?.()}
<h1>{$nav.base.name}</h1>
@@ -20,11 +23,11 @@ export let nav: FSNavigator
Size: {formatDataVolume($nav.base.file_size, 3)}<br/>
Upload date: {formatDate($nav.base.created, true, true, false)}
<hr/>
<button class="button_highlight" on:click={() => {dispatch("download")}}>
<button class="button_highlight" onclick={() => {dispatch("download")}}>
<i class="icon">download</i>
<span>Download</span>
</button>
<button on:click={() => {dispatch("details")}}>
<button onclick={() => {dispatch("details")}}>
<i class="icon">help</i>
<span>Details</span>
</button>

View File

@@ -16,12 +16,14 @@ import type { FSNavigator } from "filesystem/FSNavigator";
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
export let nav: FSNavigator
export let upload_widget: FsUploadWidget
export let edit_window: EditWindow
let { nav, upload_widget, edit_window }: {
nav: FSNavigator;
upload_widget: FsUploadWidget;
edit_window: EditWindow;
} = $props();
let viewer: any
let viewer_type = ""
let viewer: any = $state()
let viewer_type = $state("")
let last_path = ""
onMount(() => nav.subscribe(state_update))

View File

@@ -6,13 +6,16 @@ import type { FSNavigator } from "filesystem/FSNavigator";
let dispatch = createEventDispatcher();
export let nav: FSNavigator
let container: HTMLDivElement
let zoom = false
let { nav }: {
nav: FSNavigator;
} = $props();
let container: HTMLDivElement = $state()
let zoom = $state(false)
let x = 0, y = 0
let dragging = false
let swipe_prev = true
let swipe_next = true
let swipe_prev = $state(true)
let swipe_next = $state(true)
export const update = async () => {
dispatch("loading", true)
@@ -66,7 +69,7 @@ const mouseup = (e: MouseEvent) => {
}
</script>
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
<svelte:window onmousemove={mousemove} onmouseup={mouseup} />
<div
bind:this={container}
@@ -80,12 +83,12 @@ const mouseup = (e: MouseEvent) => {
on_next: () => nav.open_sibling(1),
}}
>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<img
on:dblclick={() => {zoom = !zoom}}
on:mousedown={mousedown}
on:load={on_load}
on:error={on_load}
ondblclick={() => {zoom = !zoom}}
onmousedown={mousedown}
onload={on_load}
onerror={on_load}
class="image"
class:zoom
src={fs_path_url($nav.base.path)}

View File

@@ -2,7 +2,9 @@
import { fs_path_url } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator";
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
</script>
<iframe

View File

@@ -3,8 +3,12 @@ import { tick } from "svelte";
import { fs_path_url, type FSNode } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator";
export let nav: FSNavigator
let text_type = "text"
let { nav, children }: {
nav: FSNavigator;
children?: import('svelte').Snippet;
} = $props();
let text_type = $state("text")
export const update = () => {
console.debug("Loading text file", nav.base.name)
@@ -25,7 +29,7 @@ export const update = () => {
}
}
let text_pre: HTMLPreElement
let text_pre: HTMLPreElement = $state()
const text = async (file: FSNode) => {
text_type = "text"
await tick()
@@ -42,7 +46,7 @@ const text = async (file: FSNode) => {
})
}
let md_container: HTMLElement
let md_container: HTMLElement = $state()
const markdown = async (file: FSNode) => {
text_type = "markdown"
await tick()
@@ -61,7 +65,7 @@ const markdown = async (file: FSNode) => {
</script>
<div class="container">
<slot></slot>
{@render children?.()}
{#if text_type === "markdown"}
<section bind:this={md_container} class="md">

View File

@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
export type TorrentInfo = {
trackers: string[]
comment: string,
@@ -26,9 +26,12 @@ import { loading_finish, loading_start } from "lib/Loading";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
let { nav, children }: {
nav: FSNavigator;
children?: import('svelte').Snippet;
} = $props();
let status = "loading"
let status = $state("loading")
export const update = async () => {
try {
@@ -64,11 +67,11 @@ export const update = async () => {
}
}
let torrent: TorrentInfo = {} as TorrentInfo
let magnet = ""
let torrent: TorrentInfo = $state({} as TorrentInfo)
let magnet = $state("")
</script>
<slot></slot>
{@render children?.()}
<h1>{$nav.base.name}</h1>
@@ -93,7 +96,7 @@ let magnet = ""
Torrent file could not be parsed. It may be corrupted.
</p>
{/if}
<button on:click={() => {dispatch("download")}} class="button">
<button onclick={() => {dispatch("download")}} class="button">
<i class="icon">download</i>
<span>Download torrent file</span>
</button>

View File

@@ -1,8 +1,11 @@
<script lang="ts">
import TorrentItem from './TorrentItem.svelte';
import { formatDataVolume } from "util/Formatting";
import type { TorrentFile } from "./Torrent.svelte";
export let item: TorrentFile = {} as TorrentFile
let { item = {} as TorrentFile }: {
item?: TorrentFile;
} = $props();
</script>
<ul class="list_open">
@@ -10,7 +13,7 @@ export let item: TorrentFile = {} as TorrentFile
<li class:list_closed={!child.children}>
{name} ({formatDataVolume(child.size, 3)})<br/>
{#if child.children}
<svelte:self item={child}></svelte:self>
<TorrentItem item={child}></TorrentItem>
{/if}
</li>
{/each}

View File

@@ -5,16 +5,18 @@ import { fs_path_url } from "lib/FilesystemAPI";
import type { FSNavigator } from "filesystem/FSNavigator";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
let { nav }: {
nav: FSNavigator;
} = $props();
// Used to detect when the file path changes
let last_path = ""
let loaded = false
let loaded = $state(false)
let player: HTMLVideoElement
let playing = false
let player: HTMLVideoElement = $state()
let playing = $state(false)
let media_session = false
let loop = false
let loop = $state(false)
export const update = async () => {
if (media_session) {
@@ -85,8 +87,7 @@ const video_keydown = (e: KeyboardEvent) => {
{#if
$nav.base.file_type === "video/x-matroska" ||
$nav.base.file_type === "video/quicktime" ||
$nav.base.file_type === "video/x-ms-asf"
}
$nav.base.file_type === "video/x-ms-asf"}
<div class="compatibility_warning">
This video file type is not compatible with every web
browser. If the video fails to play you can try downloading
@@ -96,7 +97,7 @@ const video_keydown = (e: KeyboardEvent) => {
<div class="player_and_controls">
<div class="player">
{#if loaded}
<!-- svelte-ignore a11y-media-has-caption -->
<!-- svelte-ignore a11y_media_has_caption -->
<video
bind:this={player}
controls
@@ -104,9 +105,9 @@ const video_keydown = (e: KeyboardEvent) => {
autoplay
loop={loop}
class="video"
on:pause={() => playing = false }
on:play={() => playing = true }
on:keydown={video_keydown}
onpause={() => playing = false}
onplay={() => playing = true}
onkeydown={video_keydown}
use:video_position={() => $nav.base.sha256_sum.substring(0, 8)}
>
<source src={fs_path_url($nav.base.path)} type={$nav.base.file_type} />
@@ -116,34 +117,34 @@ const video_keydown = (e: KeyboardEvent) => {
<div class="controls">
<div class="spacer"></div>
<button on:click={() => dispatch("open_sibling", -1) }>
<button onclick={() => dispatch("open_sibling", -1)}>
<i class="icon">skip_previous</i>
</button>
<button on:click={() => seek(-10)}>
<button onclick={() => seek(-10)}>
<i class="icon">replay_10</i>
</button>
<button on:click={toggle_playback} class="button_highlight">
<button onclick={toggle_playback} class="button_highlight">
{#if playing}
<i class="icon">pause</i>
{:else}
<i class="icon">play_arrow</i>
{/if}
</button>
<button on:click={() => seek(10)}>
<button onclick={() => seek(10)}>
<i class="icon">forward_10</i>
</button>
<button on:click={() => dispatch("open_sibling", 1) }>
<button onclick={() => dispatch("open_sibling", 1)}>
<i class="icon">skip_next</i>
</button>
<div style="width: 16px; height: 8px;"></div>
<button on:click={toggle_mute} class:button_red={player && player.muted}>
<button onclick={toggle_mute} class:button_red={player && player.muted}>
{#if player && player.muted}
<i class="icon">volume_off</i>
{:else}
<i class="icon">volume_up</i>
{/if}
</button>
<button on:click={toggle_fullscreen}>
<button onclick={toggle_fullscreen}>
<i class="icon">fullscreen</i>
</button>
<div class="spacer"></div>

View File

@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
export type ZipEntry = {
size: number,
children?: {[index: string]: ZipEntry},
@@ -19,15 +19,18 @@ import { loading_finish, loading_start } from "lib/Loading";
let dispatch = createEventDispatcher()
export let nav: FSNavigator
let { nav, children }: {
nav: FSNavigator;
children?: import('svelte').Snippet;
} = $props();
let status = "loading"
let status = $state("loading")
let zip: ZipEntry = {size: 0} as ZipEntry
let zip: ZipEntry = $state({size: 0} as ZipEntry)
let uncomp_size = 0
let comp_ratio = 0
let archive_type = ""
let truncated = false
let comp_ratio = $state(0)
let archive_type = $state("")
let truncated = $state(false)
export const update = async () => {
if (nav.base.file_type === "application/x-7z-compressed") {
@@ -93,7 +96,7 @@ const recursive_size = (file: ZipEntry) => {
}
</script>
<slot></slot>
{@render children?.()}
<h1>{$nav.base.name}</h1>
@@ -110,7 +113,7 @@ const recursive_size = (file: ZipEntry) => {
{/if}
Uploaded on: {formatDate($nav.base.created, true, true, true)}
<br/>
<button class="button_highlight" on:click={() => {dispatch("download")}}>
<button class="button_highlight" onclick={() => {dispatch("download")}}>
<i class="icon">download</i>
<span>Download</span>
</button>

View File

@@ -1,8 +1,11 @@
<script lang="ts">
import ZipItem from './ZipItem.svelte';
import type { ZipEntry } from "filesystem/viewers/Zip.svelte";
import { formatDataVolume } from "util/Formatting";
export let item: ZipEntry = {} as ZipEntry
let { item = {} as ZipEntry }: {
item?: ZipEntry;
} = $props();
</script>
<!-- First get directories and render them as details collapsibles -->
@@ -23,7 +26,7 @@ export let item: ZipEntry = {} as ZipEntry
<!-- Performance optimization, only render children if details is expanded -->
{#if child.details_open}
<svelte:self item={child}></svelte:self>
<ZipItem item={child}></ZipItem>
{/if}
</details>
{/if}