Move search bar to the file manager
This commit is contained in:
@@ -64,12 +64,12 @@ let done = () => {
|
|||||||
modal.hide()
|
modal.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const keydown = (e) => {
|
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
|
return // prevent custom shortcuts from interfering with system shortcuts
|
||||||
}
|
} else if (document.activeElement.type && document.activeElement.type === "text") {
|
||||||
if (document.activeElement.type && document.activeElement.type === "text") {
|
|
||||||
return // Prevent shortcuts from interfering with input fields
|
return // Prevent shortcuts from interfering with input fields
|
||||||
}
|
}
|
||||||
if (e.key === "/") {
|
if (e.key === "/") {
|
||||||
@@ -77,7 +77,6 @@ const keydown = (e) => {
|
|||||||
input_search.focus()
|
input_search.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={keydown} />
|
<svelte:window on:keydown={keydown} />
|
||||||
|
@@ -28,11 +28,11 @@ export class FSNavigator {
|
|||||||
// Instead of reloading the page we use the navigator to navigate to the
|
// Instead of reloading the page we use the navigator to navigate to the
|
||||||
// new page
|
// new page
|
||||||
if (history_enabled) {
|
if (history_enabled) {
|
||||||
window.onpopstate = () => {
|
window.addEventListener("popstate", () => {
|
||||||
// Get the part of the URL after the fs root and navigate to it
|
// Get the part of the URL after the fs root and navigate to it
|
||||||
const path = document.location.pathname.replace("/d/", "")
|
const path = document.location.pathname.replace("/d/", "")
|
||||||
this.navigate(decodeURIComponent(path), false)
|
this.navigate(decodeURIComponent(path), false)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ export class FSNavigator {
|
|||||||
// greeted to a 404 page when refreshing after renaming a file
|
// greeted to a 404 page when refreshing after renaming a file
|
||||||
if (this.history_enabled) {
|
if (this.history_enabled) {
|
||||||
window.document.title = node.path[node.base_index].name + " ~ pixeldrain"
|
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) {
|
if (push_history) {
|
||||||
window.history.pushState({}, window.document.title, url)
|
window.history.pushState({}, window.document.title, url)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -6,7 +6,6 @@ import Toolbar from './Toolbar.svelte';
|
|||||||
import Breadcrumbs from './Breadcrumbs.svelte';
|
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 SearchView from './SearchView.svelte';
|
|
||||||
import FSUploadWidget from './upload_widget/FSUploadWidget.svelte';
|
import FSUploadWidget from './upload_widget/FSUploadWidget.svelte';
|
||||||
import { fs_path_url } from './FilesystemAPI';
|
import { fs_path_url } from './FilesystemAPI';
|
||||||
import Menu from './Menu.svelte';
|
import Menu from './Menu.svelte';
|
||||||
@@ -24,7 +23,6 @@ let download_frame
|
|||||||
let details_visible = false
|
let details_visible = false
|
||||||
let edit_window
|
let edit_window
|
||||||
let edit_visible = false
|
let edit_visible = false
|
||||||
let view = "file"
|
|
||||||
|
|
||||||
const loading = writable(true)
|
const loading = writable(true)
|
||||||
const nav = new FSNavigator(true)
|
const nav = new FSNavigator(true)
|
||||||
@@ -75,10 +73,6 @@ const keydown = e => {
|
|||||||
case "r":
|
case "r":
|
||||||
nav.shuffle = !nav.shuffle
|
nav.shuffle = !nav.shuffle
|
||||||
break;
|
break;
|
||||||
case "/":
|
|
||||||
case "f":
|
|
||||||
search()
|
|
||||||
break
|
|
||||||
case "a":
|
case "a":
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
nav.open_sibling(-1)
|
nav.open_sibling(-1)
|
||||||
@@ -126,21 +120,6 @@ const download = () => {
|
|||||||
download_frame.src = fs_path_url(nav.base.path) + "?bulk_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>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={keydown} />
|
<svelte:window on:keydown={keydown} />
|
||||||
@@ -159,13 +138,10 @@ const search = async () => {
|
|||||||
bind:details_visible={details_visible}
|
bind:details_visible={details_visible}
|
||||||
edit_window={edit_window}
|
edit_window={edit_window}
|
||||||
bind:edit_visible={edit_visible}
|
bind:edit_visible={edit_visible}
|
||||||
bind:view={view}
|
|
||||||
on:download={download}
|
on:download={download}
|
||||||
on:search={search}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="file_preview">
|
<div class="file_preview">
|
||||||
{#if view === "file"}
|
|
||||||
<FilePreview
|
<FilePreview
|
||||||
bind:this={file_preview}
|
bind:this={file_preview}
|
||||||
nav={nav}
|
nav={nav}
|
||||||
@@ -174,9 +150,6 @@ const search = async () => {
|
|||||||
on:open_sibling={e => nav.open_sibling(e.detail)}
|
on:open_sibling={e => nav.open_sibling(e.detail)}
|
||||||
on:download={download}
|
on:download={download}
|
||||||
/>
|
/>
|
||||||
{:else if view === "search"}
|
|
||||||
<SearchView nav={nav} on:done={() => {view = "file"}} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -215,13 +188,13 @@ const search = async () => {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(*) {
|
:global(*) {
|
||||||
transition: background-color 0.5s,
|
transition: background-color 0.2s,
|
||||||
border 0.5s,
|
border 0.2s,
|
||||||
border-top 0.5s,
|
border-top 0.2s,
|
||||||
border-right 0.5s,
|
border-right 0.2s,
|
||||||
border-bottom 0.5s,
|
border-bottom 0.2s,
|
||||||
border-left 0.5s,
|
border-left 0.2s,
|
||||||
color 0.5s;
|
color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Viewer container */
|
/* Viewer container */
|
||||||
|
@@ -1,18 +1,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { fs_search, fs_encode_path, fs_thumbnail_url } from "./FilesystemAPI";
|
import { fs_search, fs_encode_path, fs_thumbnail_url } from "./FilesystemAPI";
|
||||||
|
|
||||||
export let nav
|
export let nav
|
||||||
|
|
||||||
let dispatch = createEventDispatcher()
|
let search_bar
|
||||||
|
|
||||||
let error = ""
|
let error = ""
|
||||||
let search_term = ""
|
let search_term = ""
|
||||||
let search_results = []
|
let search_results = []
|
||||||
|
let selected_result = 0
|
||||||
let searching = false
|
let searching = false
|
||||||
let last_searched_term = ""
|
let last_searched_term = ""
|
||||||
let last_limit = 10
|
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) => {
|
const search = async (limit = 10) => {
|
||||||
if (search_term.length < 2 || search_term.length > 100) {
|
if (search_term.length < 2 || search_term.length > 100) {
|
||||||
// These are the length limits defined by the API
|
// 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
|
searching = false
|
||||||
nav.set_loading(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 => {
|
const keyup = e => {
|
||||||
if (e.key === "Escape") {
|
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 {
|
} else {
|
||||||
search()
|
search()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submitting opens the first result
|
// Submitting opens the selected result
|
||||||
const submit_search = () => {
|
const submit_search = () => {
|
||||||
if (search_results.length !== 0) {
|
if (search_results.length !== 0) {
|
||||||
open_result(0)
|
open_result(selected_result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const open_result = index => {
|
const open_result = index => {
|
||||||
nav.navigate(search_results[index], true)
|
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>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={window_keydown} />
|
||||||
|
|
||||||
{#if error === "path_not_found" || error === "node_is_a_directory"}
|
{#if error === "path_not_found" || error === "node_is_a_directory"}
|
||||||
<div class="highlight_yellow center">
|
<div class="highlight_yellow center">
|
||||||
Search index not found. The search index is a file called
|
Search index not found. The search index is a file called
|
||||||
@@ -92,29 +150,28 @@ const open_result = index => {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
<div class="search_bar highlight_shaded center">
|
|
||||||
<form class="search_form" on:submit|preventDefault={submit_search}>
|
<form class="search_form" on:submit|preventDefault={submit_search}>
|
||||||
<i class="icon">search</i>
|
<i class="icon">search</i>
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
|
||||||
<input
|
<input
|
||||||
|
bind:this={search_bar}
|
||||||
class="term"
|
class="term"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type to search in {$nav.base.name}"
|
placeholder="Type to search in {$nav.base.name}"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
bind:value={search_term}
|
bind:value={search_term}
|
||||||
|
on:keydown={keydown}
|
||||||
on:keyup={keyup}
|
on:keyup={keyup}
|
||||||
autofocus
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results center">
|
<div class="results">
|
||||||
{#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)}
|
||||||
on:click|preventDefault={() => open_result(index)}
|
on:click|preventDefault={() => open_result(index)}
|
||||||
class="node"
|
class="node"
|
||||||
|
class:node_selected={selected_result === index}
|
||||||
>
|
>
|
||||||
<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
|
<img src={fs_thumbnail_url(result, 32, 32)} class="node_icon" alt="icon"/>
|
||||||
<span class="node_name">
|
<span class="node_name">
|
||||||
@@ -135,19 +192,18 @@ const open_result = index => {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.center {
|
.center {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
max-width: 100%;
|
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 {
|
.search_form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -161,11 +217,11 @@ const open_result = index => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
margin: 8px auto 16px auto;
|
overflow-y: auto;
|
||||||
|
max-height: 90vh;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: var(--body_color);
|
background: var(--body_color);
|
||||||
border-collapse: collapse;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
.results > * {
|
.results > * {
|
||||||
@@ -176,13 +232,14 @@ const open_result = index => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
padding: 4px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
.node:not(:last-child) {
|
.node_selected {
|
||||||
border-bottom: 1px solid var(--separator);
|
background: var(--highlight_background);
|
||||||
|
color: var(--highlight_text_color);
|
||||||
}
|
}
|
||||||
.node:hover:not(.node_selected) {
|
.node:hover:not(.node_selected) {
|
||||||
background: var(--input_background);
|
background: var(--input_background);
|
@@ -7,8 +7,6 @@ import FileStats from "./FileStats.svelte";
|
|||||||
let dispatch = createEventDispatcher()
|
let dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let nav
|
export let nav
|
||||||
|
|
||||||
export let view = "file"
|
|
||||||
export let details_visible = false
|
export let details_visible = false
|
||||||
export let edit_window
|
export let edit_window
|
||||||
export let edit_visible = false
|
export let edit_visible = false
|
||||||
@@ -93,13 +91,6 @@ let expand = e => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
<div class="separator hidden_horizontal"></div>
|
||||||
|
|
||||||
<button on:click={() => dispatch("download")}>
|
<button on:click={() => dispatch("download")}>
|
||||||
|
@@ -8,6 +8,7 @@ import Button from '../../layout/Button.svelte';
|
|||||||
import FileImporter from './FileImporter.svelte';
|
import FileImporter from './FileImporter.svelte';
|
||||||
import { formatDate } from '../../util/Formatting.svelte';
|
import { formatDate } from '../../util/Formatting.svelte';
|
||||||
import { drop_target } from "../../lib/DropTarget.ts"
|
import { drop_target } from "../../lib/DropTarget.ts"
|
||||||
|
import SearchBar from '../SearchBar.svelte';
|
||||||
|
|
||||||
export let nav
|
export let nav
|
||||||
export let upload_widget
|
export let upload_widget
|
||||||
@@ -266,6 +267,11 @@ onMount(() => {
|
|||||||
>
|
>
|
||||||
<div class="width_container">
|
<div class="width_container">
|
||||||
{#if mode === "viewing"}
|
{#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">
|
<div class="toolbar">
|
||||||
<button on:click={navigate_back} title="Back">
|
<button on:click={navigate_back} title="Back">
|
||||||
<i class="icon">arrow_back</i>
|
<i class="icon">arrow_back</i>
|
||||||
@@ -420,7 +426,8 @@ onMount(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 0;
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ const dispatch = createEventDispatcher();
|
|||||||
export const show = () => { set_visible(true) }
|
export const show = () => { set_visible(true) }
|
||||||
export const hide = () => { set_visible(false) }
|
export const hide = () => { set_visible(false) }
|
||||||
export const toggle = () => { set_visible(!visible) }
|
export const toggle = () => { set_visible(!visible) }
|
||||||
|
export const is_visible = () => { return visible }
|
||||||
export const set_visible = vis => {
|
export const set_visible = vis => {
|
||||||
visible = vis
|
visible = vis
|
||||||
dispatch("is_visible", visible)
|
dispatch("is_visible", visible)
|
||||||
|
Reference in New Issue
Block a user