Rebounce navigation events

This commit is contained in:
2026-01-30 12:26:36 +01:00
parent 5ce6b0c417
commit d3d26c6ae1
8 changed files with 56 additions and 26 deletions

View File

@@ -501,7 +501,7 @@ input[type="color"]:focus {
color: var(--input_text); color: var(--input_text);
text-decoration: none; text-decoration: none;
background: var(--input_hover_background); background: var(--input_hover_background);
box-shadow: 0px 0px 0px 1px var(--highlight_color); box-shadow: 0px 0px 0px 1px var(--highlight_color) !important;
} }
button:active, button:active,
@@ -515,8 +515,7 @@ input[type="color"]:active {
} }
.button_highlight { .button_highlight {
background: var(--highlight_background) !important; box-shadow: 0px 0px 0px 1px var(--highlight_color) !important;
color: var(--highlight_text_color) !important;
} }
.button_red { .button_red {

View File

@@ -53,12 +53,19 @@ export class FSNavigator {
} }
} }
last_requested_path: string = ""
navigate = async (path: string, push_history: boolean) => { navigate = async (path: string, push_history: boolean) => {
if (path === this.last_requested_path) {
console.debug("FSNavigator: Requested path is equal to current path. Debouncing")
return
}
this.last_requested_path = path
if (path[0] !== "/") { if (path[0] !== "/") {
path = "/" + path path = "/" + path
} }
console.debug("Navigating to path", path, push_history) console.debug("FSNavigator: Navigating to path", path, push_history)
try { try {
loading_start() loading_start()

View File

@@ -69,7 +69,7 @@ const file_event: FileActionHandler = (action: FileAction, index: number, orig:
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) {
select_node(index) select_node(index)
} else { } else {
file_menu.open(nav.children[index], orig.target) file_menu.open(nav.children[index], orig.target, orig)
} }
break break
case FileAction.Edit: case FileAction.Edit:
@@ -86,7 +86,7 @@ const file_event: FileActionHandler = (action: FileAction, index: number, orig:
select_node(index) select_node(index)
break break
case FileAction.Menu: case FileAction.Menu:
file_menu.open(nav.children[index], orig.target) file_menu.open(nav.children[index], orig.target, orig)
break break
} }
} }

View File

@@ -19,16 +19,23 @@ let {
let dialog: Dialog = $state() let dialog: Dialog = $state()
let node: FSNode = $state(null) let node: FSNode = $state(null)
export const open = async (n: FSNode, target: EventTarget) => { export const open = async (n: FSNode, target: EventTarget, event: Event) => {
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(el.getBoundingClientRect())
let el: HTMLElement = (target as Element).closest("button")
if (el !== null) {
dialog.open(el.getBoundingClientRect())
return
}
if (event instanceof MouseEvent) {
dialog.open(event)
return
}
console.error("Cannot find suitable target for spawning dialog window")
} }
const delete_node = async () => { const delete_node = async () => {

View File

@@ -4,7 +4,7 @@ let { children }: {
} = $props(); } = $props();
let dialog: HTMLDialogElement = $state() let dialog: HTMLDialogElement = $state()
export const open = (button_rect: DOMRect) => { export const open = (origin: DOMRect|MouseEvent) => {
// Show the window so we can get the location // Show the window so we can get the location
dialog.showModal() dialog.showModal()
@@ -15,10 +15,18 @@ export const open = (button_rect: DOMRect) => {
const max_left = window.innerWidth - dialog_rect.width - edge_offset const max_left = window.innerWidth - dialog_rect.width - edge_offset
const max_top = window.innerHeight - dialog_rect.height - edge_offset const max_top = window.innerHeight - dialog_rect.height - edge_offset
// Position the dialog in horizontally in the center of the button and let min_left: number, min_top: number
// verticially below it if (origin instanceof DOMRect) {
const min_left = Math.max((button_rect.left + (button_rect.width/2)) - (dialog_rect.width/2), edge_offset) // Position the dialog in horizontally in the center of the button and
const min_top = Math.max(button_rect.bottom, edge_offset) // verticially below it
min_left = Math.max((origin.left + (origin.width/2)) - (dialog_rect.width/2), edge_offset)
min_top = Math.max(origin.bottom, edge_offset)
} else if (origin instanceof MouseEvent) {
// Place the dialog at the bottom right of the mouse pointer, like
// regular context menus
min_left = Math.max(origin.clientX, edge_offset)
min_top = Math.max(origin.clientY, edge_offset)
}
// Place the window // Place the window
dialog.style.left = Math.round(Math.min(min_left, max_left)) + "px" dialog.style.left = Math.round(Math.min(min_left, max_left)) + "px"
@@ -35,6 +43,7 @@ export const close = () => {
// the dialog itself then the click was on the dialog background // the dialog itself then the click was on the dialog background
const click = (e: MouseEvent) => { const click = (e: MouseEvent) => {
if (e.target === dialog) { if (e.target === dialog) {
e.preventDefault()
dialog.close() dialog.close()
} }
} }
@@ -42,7 +51,7 @@ const click = (e: MouseEvent) => {
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<dialog bind:this={dialog} onclick={click}> <dialog bind:this={dialog} onclick={click} oncontextmenu={click}>
{@render children?.()} {@render children?.()}
</dialog> </dialog>

View File

@@ -63,7 +63,11 @@ onMount(async () => {
let current_page: Tab = $state(null) let current_page: Tab = $state(null)
const load_page = (pathname: string, history: boolean): boolean => { const load_page = (pathname: string, history: boolean): boolean => {
console.debug("Navigating to page", pathname, "log history:", history) console.debug(
"Page router: Navigating to page", pathname,
"current path", window.location.pathname,
"log history:", history,
)
const path_decoded = decodeURIComponent(pathname) const path_decoded = decodeURIComponent(pathname)
let page_by_path: Tab = null let page_by_path: Tab = null

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { global_navigator } from "filesystem/FSNavigator"; import { global_navigator } from "filesystem/FSNavigator";
import { fs_encode_path, FSNode } from "lib/FilesystemAPI.svelte"; import { fs_encode_path, FSNode } from "lib/FilesystemAPI.svelte";
import { highlight_current_page } from "lib/HighlightCurrentPage";
import { onMount } from "svelte"; import { onMount } from "svelte";
let siblings: FSNode[] = $state([]) let siblings: FSNode[] = $state([])
@@ -25,9 +26,9 @@ onMount(() => {
<div class="row"> <div class="row">
<a class="button" href="/d{fs_encode_path(node.path)}"> <a class="button" href="/d{fs_encode_path(node.path)}">
{#if node.is_shared()} {#if node.is_shared()}
<i class="icon">folder_shared</i> <i class="icon small">folder_shared</i>
{:else} {:else}
<i class="icon">folder</i> <i class="icon small">folder</i>
{/if} {/if}
<span>{node.name}</span> <span>{node.name}</span>
</a> </a>
@@ -52,11 +53,13 @@ onMount(() => {
{#each siblings as node} {#each siblings as node}
{#if !node.is_hidden()} {#if !node.is_hidden()}
<div class="row"> <div class="row">
<a class="button" href="/d{fs_encode_path(node.path)}"> <a class="button" href="/d{fs_encode_path(node.path)}" use:highlight_current_page>
{#if node.type === "dir"} {#if node.is_shared()}
<i class="icon">folder</i> <i class="icon small">folder_shared</i>
{:else if node.type === "dir"}
<i class="icon small">folder</i>
{:else} {:else}
<i class="icon">image</i> <i class="icon small">image</i>
{/if} {/if}
<span>{node.name}</span> <span>{node.name}</span>
</a> </a>

View File

@@ -21,6 +21,7 @@ export default defineConfig({
outDir: builddir, outDir: builddir,
emptyOutDir: true, emptyOutDir: true,
minify: production, minify: production,
sourcemap: !production,
lib: { lib: {
entry: "src/wrap.js", entry: "src/wrap.js",
name: "fnx_web", name: "fnx_web",