Rebounce navigation events
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user