Add search view

This commit is contained in:
2023-05-25 17:06:17 +02:00
parent fff705bc2a
commit 9e5f2ae6d4
5 changed files with 289 additions and 66 deletions

View File

@@ -1,5 +1,5 @@
<script>
import { onMount } from 'svelte';
import { onMount, tick } from 'svelte';
import PixeldrainLogo from '../util/PixeldrainLogo.svelte';
import LoadingIndicator from '../util/LoadingIndicator.svelte';
import EditWindow from './EditWindow.svelte';
@@ -9,6 +9,7 @@ import Breadcrumbs from './Breadcrumbs.svelte';
import DetailsWindow from './DetailsWindow.svelte';
import Navigator from './Navigator.svelte';
import FilePreview from './viewers/FilePreview.svelte';
import SearchView from './SearchView.svelte';
let loading = true
let toolbar_visible = (window.innerWidth > 600)
@@ -17,6 +18,7 @@ let download_frame
let details_visible = false
let edit_window
let edit_visible = false
let view = "file"
let fs_navigator
let state = {
@@ -67,6 +69,10 @@ const keydown = e => {
case "r":
state.shuffle = !state.shuffle
break;
case "/":
case "f":
view = "search"
break
case "a":
case "ArrowLeft":
fs_navigator.open_sibling(-1)
@@ -76,6 +82,8 @@ const keydown = e => {
fs_navigator.open_sibling(1)
break;
}
e.preventDefault()
};
const download = () => {
@@ -90,7 +98,11 @@ const download = () => {
<Navigator
bind:this={fs_navigator}
bind:state
on:navigation_complete={() => file_preview.state_update()}
on:navigation_complete={async () => {
view = "file";
await tick();
file_preview.state_update();
}}
on:loading={e => loading = e.detail}
/>
@@ -111,7 +123,7 @@ const download = () => {
</div>
</div>
<div class="file_preview">
<div class="viewer_area">
<Toolbar
visible={toolbar_visible}
fs_navigator={fs_navigator}
@@ -119,19 +131,29 @@ const download = () => {
bind:details_visible={details_visible}
edit_window={edit_window}
bind:edit_visible={edit_visible}
bind:view={view}
on:download={download}
/>
<FilePreview
bind:this={file_preview}
fs_navigator={fs_navigator}
state={state}
toolbar_visible={toolbar_visible}
edit_window={edit_window}
on:loading={e => {loading = e.detail}}
on:open_sibling={e => fs_navigator.open_sibling(e.detail)}
on:download={download}
/>
<div class="file_preview checkers" class:toolbar_visible>
{#if view === "file"}
<FilePreview
bind:this={file_preview}
fs_navigator={fs_navigator}
state={state}
edit_window={edit_window}
on:loading={e => {loading = e.detail}}
on:open_sibling={e => fs_navigator.open_sibling(e.detail)}
on:download={download}
/>
{:else if view === "search"}
<SearchView
state={state}
fs_navigator={fs_navigator}
on:loading={e => {loading = e.detail}}
/>
{/if}
</div>
</div>
<!-- This frame will load the download URL when a download button is pressed -->
@@ -210,7 +232,7 @@ const download = () => {
}
}
/* File preview area (row 2) */
.file_preview {
.viewer_area {
flex-grow: 1;
flex-shrink: 1;
position: relative;
@@ -220,4 +242,24 @@ const download = () => {
margin: 0;
z-index: 9;
}
.file_preview {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: block;
min-height: 100px;
min-width: 100px;
transition: left 0.25s;
overflow: auto;
text-align: center;
border-radius: 8px;
border: 2px solid var(--separator);
}
.file_preview.toolbar_visible {
left: 8em;
}
</style>

View File

@@ -82,3 +82,15 @@ export const fs_delete_all = async (bucket, path) => {
throw new Error(await resp.text())
}
}
export const fs_search = async (bucket, path, term, limit = 10) => {
const resp = await fetch(
fs_path_url(bucket, path) +
"?search=" + encodeURIComponent(term) +
"&limit=" + limit
)
if (resp.status >= 400) {
throw await resp.text()
}
return resp.json()
}

View File

@@ -0,0 +1,174 @@
<script>
import { createEventDispatcher } from "svelte";
import { fs_search } from "./FilesystemAPI";
import { fs_thumbnail_url } from "./FilesystemUtil";
export let state
export let fs_navigator
let dispatch = createEventDispatcher()
let search_term = ""
let search_results = []
let searching = false
let last_searched_term = ""
let last_limit = 10
const search = async (limit = 10) => {
if (search_term.length < 2 || search_term.length > 100) {
// These are the length limits defined by the API
search_results = []
return
} else if (search_term === last_searched_term && limit === last_limit) {
// If the term is the same we don't have to search
return
} else if (searching) {
// If a search is already running we also don't search
return
}
console.debug("Searching for", search_term)
last_searched_term = search_term
last_limit = limit
searching = true
dispatch("loading", true)
try {
search_results = await fs_search(state.root.id, state.base.path, search_term, limit)
} catch (err) {
alert(err)
console.error(err)
}
searching = false
dispatch("loading", false)
// It's possible that the user entered another letter while we were
// performing the search reqeust. If this happens we run the search function
// again
if (last_searched_term !== search_term) {
console.debug("Search term changed during search. Searching again")
await search()
}
}
// Submitting opens the first result
const submit_search = () => {
if (search_results.length !== 0) {
fs_navigator.navigate(search_results[0], true)
}
}
const node_click = index => {
fs_navigator.navigate(search_results[index], true)
}
</script>
<form class="search_bar highlight_shaded" on:submit|preventDefault={submit_search}>
<i class="icon">search</i>
<!-- svelte-ignore a11y-autofocus -->
<input class="term" type="text" style="width: 100%;" bind:value={search_term} on:keyup={() => search()} autofocus />
<input class="submit" type="submit" value="Search"/>
</form>
<div class="directory">
<tr>
<td></td>
<td>Name</td>
</tr>
{#each search_results as result, index}
<a
href={state.path_root+result}
on:click|preventDefault={() => node_click(index)}
class="node"
>
<td>
<img src={fs_thumbnail_url(state.root.id, result)} class="node_icon" alt="icon"/>
</td>
<td class="node_name">
{result}
</td>
</a>
{/each}
{#if search_results.length === last_limit}
<div class="node">
<td></td>
<td class="node_name">
<button on:click={() => {search(last_limit + 100)}}>
<i class="icon">expand_more</i>
More results
</button>
</td>
</div>
{/if}
</div>
<style>
.search_bar {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
max-width: 1000px;
margin: 10px auto;
}
.term {
flex: 1 1 auto;
}
.submit {
flex: 0 0 auto;
}
.directory {
display: table;
position: relative;
overflow: hidden;
margin: 8px auto 16px auto;
text-align: left;
background: var(--body_color);
border-collapse: collapse;
border-radius: 8px;
max-width: 100%;
width: 1000px;
}
.directory > * {
display: table-row;
}
.directory > * > * {
display: table-cell;
}
.node {
display: table-row;
text-decoration: none;
color: var(--text-color);
padding: 6px;
}
.node:not(:last-child) {
border-bottom: 1px solid var(--separator);
}
.node:hover:not(.node_selected) {
background: var(--input_background);
color: var(--input_text);
text-decoration: none;
}
td {
padding: 4px;
vertical-align: middle;
}
.node_icon {
height: 32px;
width: 32px;
vertical-align: middle;
border-radius: 4px;
}
.node_name {
width: 100%;
overflow: hidden;
line-height: 1.2em;
word-break: break-all;
}
</style>

View File

@@ -17,6 +17,7 @@ export let state = {
shuffle: false
}
export let view = "file"
export let details_visible = false
export let edit_window
export let edit_visible = false
@@ -63,6 +64,13 @@ let share = async () => {
sharebar_visible = !sharebar_visible
}
}
let toggle_search = () => {
if (view === "search") {
view = "file"
} else {
view = "search"
}
}
</script>
<div class="toolbar" class:toolbar_visible={visible}>
@@ -94,6 +102,12 @@ let share = async () => {
</button>
</div>
{#if state.root.id === "me" && state.base.type === "dir"}
<button on:click={toggle_search} class="toolbar_button" class:button_highlight={view === "search"}>
<i class="icon">search</i> Search
</button>
{/if}
{#if state.base.type === "file"}
<button on:click={() => dispatch("download")} class="toolbar_button">
<i class="icon">save</i> Download

View File

@@ -1,5 +1,5 @@
<script>
import { tick } from "svelte";
import { onMount, tick } from "svelte";
import Spinner from "../../util/Spinner.svelte";
import { fs_node_type } from "../FilesystemUtil";
import FileManager from "../filemanager/FileManager.svelte";
@@ -13,7 +13,6 @@ import Torrent from "./Torrent.svelte";
import Zip from "./Zip.svelte";
export let fs_navigator
export let toolbar_visible
export let edit_window
export let state
@@ -32,60 +31,42 @@ export const state_update = async () => {
viewer.update()
}
}
onMount(() => {
state_update()
})
</script>
<div class="file_preview checkers" class:toolbar_visible>
{#if viewer_type === ""}
<div class="center">
<Spinner></Spinner>
</div>
{:else if viewer_type === "dir"}
<FileManager
fs_navigator={fs_navigator}
state={state}
edit_window={edit_window}
on:loading
/>
{:else if viewer_type === "audio"}
<Audio state={state} on:open_sibling/>
{:else if viewer_type === "image"}
<Image state={state} on:open_sibling/>
{:else if viewer_type === "video"}
<Video state={state} bind:this={viewer} on:open_sibling/>
{:else if viewer_type === "pdf"}
<Pdf state={state}/>
{:else if viewer_type === "text"}
<Text state={state}/>
{:else if viewer_type === "torrent"}
<Torrent state={state} bind:this={viewer} on:loading on:download/>
{:else if viewer_type === "zip"}
<Zip state={state} bind:this={viewer} on:loading on:download />
{:else}
<File state={state} on:download/>
{/if}
</div>
{#if viewer_type === ""}
<div class="center">
<Spinner></Spinner>
</div>
{:else if viewer_type === "dir"}
<FileManager
fs_navigator={fs_navigator}
state={state}
edit_window={edit_window}
on:loading
/>
{:else if viewer_type === "audio"}
<Audio state={state} on:open_sibling/>
{:else if viewer_type === "image"}
<Image state={state} on:open_sibling/>
{:else if viewer_type === "video"}
<Video state={state} bind:this={viewer} on:open_sibling/>
{:else if viewer_type === "pdf"}
<Pdf state={state}/>
{:else if viewer_type === "text"}
<Text state={state}/>
{:else if viewer_type === "torrent"}
<Torrent state={state} bind:this={viewer} on:loading on:download/>
{:else if viewer_type === "zip"}
<Zip state={state} bind:this={viewer} on:loading on:download />
{:else}
<File state={state} on:download/>
{/if}
<style>
.file_preview {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: block;
min-height: 100px;
min-width: 100px;
transition: left 0.25s;
overflow: auto;
text-align: center;
border-radius: 8px;
border: 2px solid var(--separator);
}
.file_preview.toolbar_visible {
left: 8em;
}
.center{
position: relative;
display: block;