Move search bar to the file manager

This commit is contained in:
2024-11-14 16:14:58 +01:00
parent 4760f7ae04
commit 0043fe2e9e
7 changed files with 135 additions and 107 deletions

View File

@@ -64,12 +64,12 @@ let done = () => {
modal.hide()
}
const keydown = (e) => {
if (e.ctrlKey || e.altKey || e.metaKey) {
if (!modal.is_visible()) {
return // Prevent a closed window from catching key events
} else if (e.ctrlKey || e.altKey || e.metaKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
if (document.activeElement.type && document.activeElement.type === "text") {
} else if (document.activeElement.type && document.activeElement.type === "text") {
return // Prevent shortcuts from interfering with input fields
}
if (e.key === "/") {
@@ -77,7 +77,6 @@ const keydown = (e) => {
input_search.focus()
}
}
</script>
<svelte:window on:keydown={keydown} />

View File

@@ -28,11 +28,11 @@ export class FSNavigator {
// Instead of reloading the page we use the navigator to navigate to the
// new page
if (history_enabled) {
window.onpopstate = () => {
window.addEventListener("popstate", () => {
// Get the part of the URL after the fs root and navigate to it
const path = document.location.pathname.replace("/d/", "")
this.navigate(decodeURIComponent(path), false)
}
})
}
}
@@ -104,7 +104,7 @@ export class FSNavigator {
// greeted to a 404 page when refreshing after renaming a file
if (this.history_enabled) {
window.document.title = node.path[node.base_index].name + " ~ pixeldrain"
const url = "/d" + fs_encode_path(node.path[node.base_index].path)
const url = "/d" + fs_encode_path(node.path[node.base_index].path) + window.location.hash
if (push_history) {
window.history.pushState({}, window.document.title, url)
} else {

View File

@@ -6,7 +6,6 @@ import Toolbar from './Toolbar.svelte';
import Breadcrumbs from './Breadcrumbs.svelte';
import DetailsWindow from './DetailsWindow.svelte';
import FilePreview from './viewers/FilePreview.svelte';
import SearchView from './SearchView.svelte';
import FSUploadWidget from './upload_widget/FSUploadWidget.svelte';
import { fs_path_url } from './FilesystemAPI';
import Menu from './Menu.svelte';
@@ -24,7 +23,6 @@ let download_frame
let details_visible = false
let edit_window
let edit_visible = false
let view = "file"
const loading = writable(true)
const nav = new FSNavigator(true)
@@ -75,10 +73,6 @@ const keydown = e => {
case "r":
nav.shuffle = !nav.shuffle
break;
case "/":
case "f":
search()
break
case "a":
case "ArrowLeft":
nav.open_sibling(-1)
@@ -126,21 +120,6 @@ const download = () => {
download_frame.src = fs_path_url(nav.base.path) + "?bulk_download"
}
}
const search = async () => {
if (view === "search") {
view = "file"
return
}
// If we are not currently in a directory, then we navigate up to the parent
// directory
if (nav.base.type !== "dir") {
await nav.navigate_up()
}
view = "search"
}
</script>
<svelte:window on:keydown={keydown} />
@@ -159,24 +138,18 @@ const search = async () => {
bind:details_visible={details_visible}
edit_window={edit_window}
bind:edit_visible={edit_visible}
bind:view={view}
on:download={download}
on:search={search}
/>
<div class="file_preview">
{#if view === "file"}
<FilePreview
bind:this={file_preview}
nav={nav}
upload_widget={upload_widget}
edit_window={edit_window}
on:open_sibling={e => nav.open_sibling(e.detail)}
on:download={download}
/>
{:else if view === "search"}
<SearchView nav={nav} on:done={() => {view = "file"}} />
{/if}
<FilePreview
bind:this={file_preview}
nav={nav}
upload_widget={upload_widget}
edit_window={edit_window}
on:open_sibling={e => nav.open_sibling(e.detail)}
on:download={download}
/>
</div>
</div>
@@ -215,13 +188,13 @@ const search = async () => {
<style>
:global(*) {
transition: background-color 0.5s,
border 0.5s,
border-top 0.5s,
border-right 0.5s,
border-bottom 0.5s,
border-left 0.5s,
color 0.5s;
transition: background-color 0.2s,
border 0.2s,
border-top 0.2s,
border-right 0.2s,
border-bottom 0.2s,
border-left 0.2s,
color 0.2s;
}
/* Viewer container */

View File

@@ -1,18 +1,27 @@
<script>
import { createEventDispatcher } from "svelte";
import { onMount } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "./FilesystemAPI";
export let nav
let dispatch = createEventDispatcher()
let search_bar
let error = ""
let search_term = ""
let search_results = []
let selected_result = 0
let searching = false
let last_searched_term = ""
let last_limit = 10
onMount(() => {
// Clear results when the user moves to a new directory
return nav.subscribe(nav => {
if (nav.initialized) {
clear_search(false)
}
})
})
const search = async (limit = 10) => {
if (search_term.length < 2 || search_term.length > 100) {
// These are the length limits defined by the API
@@ -46,6 +55,10 @@ const search = async (limit = 10) => {
}
}
if (search_results.length > 0 && selected_result > search_results.length-1) {
selected_result = search_results.length-1
}
searching = false
nav.set_loading(false)
@@ -58,27 +71,72 @@ const search = async (limit = 10) => {
}
}
const clear_search = (blur) => {
error = ""
search_term = ""
search_results = []
selected_result = 0
searching = false
last_searched_term = ""
last_limit = 10
// If blur is true we unfocus the search field. This should only happen when
// the user presses escape
if (blur) {
search_bar.blur()
}
}
// Cursor navigation events can only be prevented with keydown. But we want to
// use keyup for searching, so we use two listeners here
const keydown = e => {
if (e.key === "Escape" || e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault()
}
}
const keyup = e => {
if (e.key === "Escape") {
dispatch("done")
clear_search(true)
} else if (e.key === "ArrowUp") {
if (selected_result > 0) {
selected_result--
}
} else if (e.key === "ArrowDown") {
if (selected_result+1 < search_results.length) {
selected_result++
}
} else {
search()
}
}
// Submitting opens the first result
// Submitting opens the selected result
const submit_search = () => {
if (search_results.length !== 0) {
open_result(0)
open_result(selected_result)
}
}
const open_result = index => {
nav.navigate(search_results[index], true)
dispatch("done")
clear_search(false)
}
const window_keydown = (e) => {
if (e.key === "Escape" && search_term !== "") {
clear_search(true)
e.preventDefault()
} else if (e.key === "/" || e.key === "f") {
e.preventDefault()
e.stopPropagation()
search_bar.focus()
return
}
}
</script>
<svelte:window on:keydown={window_keydown} />
{#if error === "path_not_found" || error === "node_is_a_directory"}
<div class="highlight_yellow center">
Search index not found. The search index is a file called
@@ -92,48 +150,48 @@ const open_result = index => {
</div>
{/if}
<div class="search_bar highlight_shaded center">
<div class="center">
<form class="search_form" on:submit|preventDefault={submit_search}>
<i class="icon">search</i>
<!-- svelte-ignore a11y-autofocus -->
<input
bind:this={search_bar}
class="term"
type="text"
placeholder="Type to search in {$nav.base.name}"
style="width: 100%;"
bind:value={search_term}
on:keydown={keydown}
on:keyup={keyup}
autofocus
/>
</form>
</div>
<div class="results center">
{#each search_results as result, index}
<a
href={"/d"+fs_encode_path(result)}
on:click|preventDefault={() => open_result(index)}
class="node"
>
<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
<span class="node_name">
<!-- Remove the search directory from the result -->
{result.slice($nav.base.path.length+1)}
</span>
</a>
{/each}
<div class="results">
{#each search_results as result, index}
<a
href={"/d"+fs_encode_path(result)}
on:click|preventDefault={() => open_result(index)}
class="node"
class:node_selected={selected_result === index}
>
<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
<span class="node_name">
<!-- Remove the search directory from the result -->
{result.slice($nav.base.path.length+1)}
</span>
</a>
{/each}
{#if search_results.length === last_limit}
<div class="node">
<div class="node_name" style="text-align: center;">
<button on:click={() => {search(last_limit + 100)}}>
<i class="icon">expand_more</i>
More results
</button>
{#if search_results.length === last_limit}
<div class="node">
<div class="node_name" style="text-align: center;">
<button on:click={() => {search(last_limit + 100)}}>
<i class="icon">expand_more</i>
More results
</button>
</div>
</div>
</div>
{/if}
{/if}
</div>
</div>
<style>
@@ -141,13 +199,11 @@ const open_result = index => {
margin: auto;
width: 1000px;
max-width: 100%;
padding-top: 4px;
padding-bottom: 4px;
border-bottom: 1px solid var(--separator);
}
.search_bar {
display: flex;
flex-direction: column;
margin-top: 10px;
}
.search_form {
display: flex;
flex-direction: row;
@@ -161,11 +217,11 @@ const open_result = index => {
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
margin: 8px auto 16px auto;
overflow-x: hidden;
overflow-y: auto;
max-height: 90vh;
text-align: left;
background: var(--body_color);
border-collapse: collapse;
border-radius: 8px;
}
.results > * {
@@ -176,13 +232,14 @@ const open_result = index => {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
gap: 4px;
text-decoration: none;
color: var(--text-color);
padding: 4px;
padding: 2px;
}
.node:not(:last-child) {
border-bottom: 1px solid var(--separator);
.node_selected {
background: var(--highlight_background);
color: var(--highlight_text_color);
}
.node:hover:not(.node_selected) {
background: var(--input_background);

View File

@@ -7,8 +7,6 @@ import FileStats from "./FileStats.svelte";
let dispatch = createEventDispatcher()
export let nav
export let view = "file"
export let details_visible = false
export let edit_window
export let edit_visible = false
@@ -93,13 +91,6 @@ let expand = e => {
</button>
</div>
{#if $nav.path[0] && $nav.path[0].id === "me"}
<button on:click={() => dispatch("search")} class:button_highlight={view === "search"}>
<i class="icon">search</i>
<span>Search</span>
</button>
{/if}
<div class="separator hidden_horizontal"></div>
<button on:click={() => dispatch("download")}>

View File

@@ -8,6 +8,7 @@ import Button from '../../layout/Button.svelte';
import FileImporter from './FileImporter.svelte';
import { formatDate } from '../../util/Formatting.svelte';
import { drop_target } from "../../lib/DropTarget.ts"
import SearchBar from '../SearchBar.svelte';
export let nav
export let upload_widget
@@ -266,6 +267,11 @@ onMount(() => {
>
<div class="width_container">
{#if mode === "viewing"}
<!-- Search only works in the user's home directory -->
{#if $nav.path[0] && $nav.path[0].id === "me"}
<SearchBar nav={nav}/>
{/if}
<div class="toolbar">
<button on:click={navigate_back} title="Back">
<i class="icon">arrow_back</i>
@@ -420,7 +426,8 @@ onMount(() => {
width: 100%;
max-width: 1000px;
margin: auto;
padding: 0;
padding-top: 4px;
padding-bottom: 4px;
justify-content: center;
align-items: center;
}

View File

@@ -30,6 +30,7 @@ const dispatch = createEventDispatcher();
export const show = () => { set_visible(true) }
export const hide = () => { set_visible(false) }
export const toggle = () => { set_visible(!visible) }
export const is_visible = () => { return visible }
export const set_visible = vis => {
visible = vis
dispatch("is_visible", visible)