Move search bar into modal

This commit is contained in:
2026-04-01 19:38:15 +02:00
parent dbfe9ff383
commit f578f2c2c0
13 changed files with 299 additions and 202 deletions

View File

@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { fs_encode_path } from "lib/FilesystemAPI.svelte"; import { fs_encode_path } from "lib/FilesystemAPI.svelte";
import { path_link, type FSNavigator } from "./FSNavigator"; import { path_link, type FSNavigator } from "./FSNavigator";
import { menu_is_open } from "wrap/MainMenu.svelte";
import FileMenu from "./filemanager/FileMenu.svelte"; import FileMenu from "./filemanager/FileMenu.svelte";
import EditWindow from "./edit_window/EditWindow.svelte"; import EditWindow from "./edit_window/EditWindow.svelte";
import { onMount } from "svelte";
import { breadcrumbs_store } from "wrap/BreadcrumbStore";
let { let {
nav, nav,
@@ -14,9 +15,15 @@ let {
} = $props(); } = $props();
let file_menu: FileMenu = $state() let file_menu: FileMenu = $state()
onMount(() => {
return nav.subscribe(nav => {
breadcrumbs_store.set(breadcrumbs)
})
})
</script> </script>
<div class="breadcrumbs" class:menu_closed={!$menu_is_open}> {#snippet breadcrumbs()}
{#each $nav.path as node, i (node.path)} {#each $nav.path as node, i (node.path)}
<a <a
href={"/d"+fs_encode_path(node.path)} href={"/d"+fs_encode_path(node.path)}
@@ -37,23 +44,11 @@ let file_menu: FileMenu = $state()
<i class="icon">chevron_right</i> <i class="icon">chevron_right</i>
{/if} {/if}
{/each} {/each}
</div>
<FileMenu bind:this={file_menu} nav={nav} edit_window={edit_window} /> <FileMenu bind:this={file_menu} nav={nav} edit_window={edit_window} />
{/snippet}
<style> <style>
.breadcrumbs {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: left;
flex-wrap: wrap;
flex-direction: row;
overflow: hidden;
background: var(--shaded_background);
backdrop-filter: blur(4px);
border-bottom: 1px solid var(--separator);
}
.breadcrumb { .breadcrumb {
min-width: 1em; min-width: 1em;
text-align: center; text-align: center;
@@ -73,7 +68,4 @@ let file_menu: FileMenu = $state()
/* The base name uses all available space */ /* The base name uses all available space */
max-width: unset; max-width: unset;
} }
.menu_closed {
padding-left: 2em;
}
</style> </style>

View File

@@ -10,6 +10,7 @@ import { global_navigator } from "./FSNavigator"
import { css_from_path } from "filesystem/edit_window/Branding"; import { css_from_path } from "filesystem/edit_window/Branding";
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte"; import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";
import { current_page_store } from "wrap/RouterStore"; import { current_page_store } from "wrap/RouterStore";
import SearchBar from "./SearchBar.svelte";
let file_preview: FilePreview = $state() let file_preview: FilePreview = $state()
let toolbar: Toolbar = $state() let toolbar: Toolbar = $state()
@@ -18,6 +19,7 @@ let details_visible = $state(false)
let edit_window: EditWindow = $state() let edit_window: EditWindow = $state()
let edit_visible = $state(false) let edit_visible = $state(false)
let details_window: DetailsWindow = $state() let details_window: DetailsWindow = $state()
let search_bar: SearchBar = $state()
const nav = global_navigator const nav = global_navigator
@@ -85,6 +87,9 @@ const keydown = (e: KeyboardEvent) => {
case "r": case "r":
nav.shuffle = !nav.shuffle nav.shuffle = !nav.shuffle
break; break;
case "/":
search_bar.open()
break;
case "a": case "a":
case "ArrowLeft": case "ArrowLeft":
nav.open_sibling(-1) nav.open_sibling(-1)
@@ -146,6 +151,7 @@ const keydown = (e: KeyboardEvent) => {
upload_widget={upload_widget} upload_widget={upload_widget}
edit_window={edit_window} edit_window={edit_window}
details_window={details_window} details_window={details_window}
search_bar={search_bar}
/> />
</div> </div>
@@ -158,6 +164,8 @@ const keydown = (e: KeyboardEvent) => {
/> />
</div> </div>
<SearchBar bind:this={search_bar} nav={nav}/>
<DetailsWindow nav={nav} bind:this={details_window} bind:visible={details_visible} /> <DetailsWindow nav={nav} bind:this={details_window} bind:visible={details_visible} />
<EditWindow nav={nav} bind:this={edit_window} bind:visible={edit_visible} /> <EditWindow nav={nav} bind:this={edit_window} bind:visible={edit_visible} />
@@ -183,8 +191,7 @@ const keydown = (e: KeyboardEvent) => {
.filesystem { .filesystem {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; flex: 1 1 auto; /* auto grow */
width: 100%;
} }
.file_preview { .file_preview {

View File

@@ -1,13 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount, tick } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI.svelte"; import { fs_search, fs_encode_path, fs_thumbnail_url, FSNode } from "lib/FilesystemAPI.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
import Modal from "util/Modal.svelte";
let { nav }: { let { nav }: {
nav: FSNavigator; nav: FSNavigator;
} = $props(); } = $props();
let modal: Modal
let search_bar: HTMLInputElement = $state() let search_bar: HTMLInputElement = $state()
let error = $state("") let error = $state("")
let search_term = $state("") let search_term = $state("")
@@ -17,6 +19,10 @@ let searching = false
let last_searched_term = "" let last_searched_term = ""
let last_limit = $state(10) let last_limit = $state(10)
// The path in which we're searching. This is used for stripping the path prefix
// of search results, and showing the title of the Modal
let search_prefix_node: FSNode = $state({} as FSNode)
onMount(() => { onMount(() => {
// Clear results when the user moves to a new directory // Clear results when the user moves to a new directory
return nav.subscribe(nav => { return nav.subscribe(nav => {
@@ -26,6 +32,13 @@ onMount(() => {
}) })
}) })
export const open = async () => {
search_prefix_node = nav.base
modal.show()
await tick()
search_bar.focus()
}
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
@@ -49,7 +62,23 @@ const search = async (limit = 10) => {
try { try {
loading_start() loading_start()
search_results = await fs_search(nav.base.path, search_term, limit) let search_node = nav.base
// If the base is not a directory, we use the parent
if (search_node.type !== "dir") {
search_node = nav.path[nav.path.length-2]
}
let search_results_tmp = await fs_search(search_node.path, search_term, limit)
// If there are no results, and we are not searching the filesystem
// root, we will search the filesystem root
if (search_results_tmp.length === 0 && search_node.path != nav.path[0].path) {
search_node = nav.path[0]
search_results_tmp = await fs_search(search_node.path, search_term, limit)
}
search_prefix_node = search_node
search_results = search_results_tmp
} catch (err) { } catch (err) {
if (err.value) { if (err.value) {
error = err.value error = err.value
@@ -68,7 +97,7 @@ const search = async (limit = 10) => {
searching = false searching = false
// It's possible that the user entered another letter while we were // It's possible that the user entered another letter while we were
// performing the search reqeust. If this happens we run the search function // performing the search request. If this happens we run the search function
// again // again
if (last_searched_term !== search_term) { if (last_searched_term !== search_term) {
console.debug("Search term changed during search. Searching again") console.debug("Search term changed during search. Searching again")
@@ -90,6 +119,8 @@ const clear_search = (blur: boolean) => {
if (blur) { if (blur) {
search_bar.blur() search_bar.blur()
} }
modal.hide()
} }
// Cursor navigation events can only be prevented with keydown. But we want to // Cursor navigation events can only be prevented with keydown. But we want to
@@ -144,7 +175,8 @@ const window_keydown = (e: KeyboardEvent) => {
} else if (e.key === "/" || e.key === "f") { } else if (e.key === "/" || e.key === "f") {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
search_bar.focus() // search_bar.focus()
open()
return return
} }
} }
@@ -152,38 +184,33 @@ const window_keydown = (e: KeyboardEvent) => {
<svelte:window onkeydown={window_keydown} /> <svelte:window onkeydown={window_keydown} />
{#if error === "path_not_found" || error === "node_is_a_directory"}
<Modal bind:this={modal} title="Searching in {search_prefix_node.name}">
{#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
'.search_index.gz' in your home directory. If you delete this file then '.search_index.zstd' in your home directory. If you delete this file
search will not work. The file is regenerated 10 minutes after modifying then search will not work. The file is regenerated 10 minutes after
a file in your filesystem. modifying a file in your filesystem.
</div> </div>
{:else if error !== ""} {:else if error !== ""}
<div class="highlight_red center"> <div class="highlight_red center">
An error ocurred while executing the search request: {error} An error ocurred while executing the search request: {error}
</div> </div>
{/if} {/if}
<div class="center">
<div class="center">
<form class="search_form" onsubmit={submit_search}> <form class="search_form" onsubmit={submit_search}>
<i class="icon">search</i> <i class="icon">search</i>
<input <input
bind:this={search_bar} bind:this={search_bar}
class="term" class="term"
type="text" type="text"
placeholder="Press / to search in {$nav.base.name}" placeholder="Enter search term"
style="width: 100%;" style="width: 100%;"
bind:value={search_term} bind:value={search_term}
onkeydown={input_keydown} onkeydown={input_keydown}
onkeyup={input_keyup} onkeyup={input_keyup}
/> />
{#if search_term !== ""}
<!-- Button needs to be of button type in order to not submit the form -->
<button onclick={() => clear_search(false)} type="button">
<i class="icon">close</i>
</button>
{/if}
</form> </form>
<div class="results"> <div class="results">
@@ -201,7 +228,7 @@ const window_keydown = (e: KeyboardEvent) => {
<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">
<!-- Remove the search directory from the result --> <!-- Remove the search directory from the result -->
{result.slice($nav.base.path.length+1)} {result.slice(search_prefix_node.path.length+1)}
</span> </span>
</a> </a>
{/each} {/each}
@@ -217,7 +244,8 @@ const window_keydown = (e: KeyboardEvent) => {
</div> </div>
{/if} {/if}
</div> </div>
</div> </div>
</Modal>
<style> <style>
.center { .center {
@@ -247,9 +275,7 @@ const window_keydown = (e: KeyboardEvent) => {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
max-height: 80vh; max-height: 80vh;
text-align: left; text-align: initial;
background: var(--body_color);
border-radius: 8px;
} }
.results > * { .results > * {
display: flex; display: flex;

View File

@@ -113,7 +113,8 @@ export const copy_link = () => {
border-top: 1px solid var(--separator); border-top: 1px solid var(--separator);
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(4px); position: sticky;
bottom: 0;
} }
.grid { .grid {
display: grid; display: grid;

View File

@@ -96,7 +96,7 @@ const add_styles = (style: Style, properties: FSNodeProperties) => {
style.body_color = properties.brand_body_color style.body_color = properties.brand_body_color
style.body_background = properties.brand_body_color style.body_background = properties.brand_body_color
style.body_text_color = add_contrast(properties.brand_body_color, 75) style.body_text_color = add_contrast(properties.brand_body_color, 75)
style.shaded_background = set_alpha(properties.brand_body_color, 0.75) style.shaded_background = set_alpha(properties.brand_body_color, 0.9)
style.separator = add_contrast(properties.brand_body_color, 8) style.separator = add_contrast(properties.brand_body_color, 8)
style.shadow_color = darken(properties.brand_body_color, 0.8) style.shadow_color = darken(properties.brand_body_color, 0.8)
} }

View File

@@ -8,24 +8,26 @@ import CompactView from "./CompactView.svelte"
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
import { formatDate } from "util/Formatting"; import { formatDate } from "util/Formatting";
import { drop_target } from "lib/DropTarget" import { drop_target } from "lib/DropTarget"
import SearchBar from "./SearchBar.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte"; import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte"; import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import { FileAction, type FileActionHandler } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
import FileMenu from "./FileMenu.svelte"; import FileMenu from "./FileMenu.svelte";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
import SearchBar from "../SearchBar.svelte";
let { let {
nav = $bindable(), nav = $bindable(),
upload_widget, upload_widget,
edit_window = $bindable(), edit_window = $bindable(),
search_bar = $bindable(),
directory_view = $bindable(""), directory_view = $bindable(""),
children children
}: { }: {
nav: FSNavigator; nav: FSNavigator;
upload_widget: FsUploadWidget; upload_widget: FsUploadWidget;
edit_window: EditWindow; edit_window: EditWindow;
search_bar: SearchBar;
directory_view?: string; directory_view?: string;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} = $props(); } = $props();
@@ -306,6 +308,9 @@ onMount(() => {
{#if mode === "viewing"} {#if mode === "viewing"}
<div class="toolbar"> <div class="toolbar">
<div class="toolbar_left"> <div class="toolbar_left">
<button onclick={() => {search_bar.open()}} title="Press / to search">
<i class="icon">search</i>
</button>
<button onclick={navigate_back} title="Back"> <button onclick={navigate_back} title="Back">
<i class="icon">arrow_back</i> <i class="icon">arrow_back</i>
</button> </button>
@@ -356,8 +361,6 @@ onMount(() => {
</div> </div>
</div> </div>
<SearchBar nav={nav}/>
{:else if mode === "selecting"} {:else if mode === "selecting"}
<div class="toolbar toolbar_edit"> <div class="toolbar toolbar_edit">
<Button click={viewing_mode} icon="close"/> <Button click={viewing_mode} icon="close"/>

View File

@@ -4,9 +4,8 @@ import ListView from "./ListView.svelte"
import GalleryView from "./GalleryView.svelte" import GalleryView from "./GalleryView.svelte"
import CompactView from "./CompactView.svelte" import CompactView from "./CompactView.svelte"
import Modal from "util/Modal.svelte"; import Modal from "util/Modal.svelte";
import Breadcrumbs from "filesystem/Breadcrumbs.svelte" import { FSNavigator, path_link } from "filesystem/FSNavigator";
import { FSNavigator } from "filesystem/FSNavigator"; import { fs_encode_path, type FSNode } from "lib/FilesystemAPI.svelte";
import type { FSNode } from "lib/FilesystemAPI.svelte";
import { FileAction, type FileActionHandler } from "./FileManagerLib"; import { FileAction, type FileActionHandler } from "./FileManagerLib";
let nav = $state(new FSNavigator(false)) let nav = $state(new FSNavigator(false))
@@ -171,7 +170,26 @@ onMount(() => {
</div> </div>
{/snippet} {/snippet}
<Breadcrumbs nav={nav}/> {#each $nav.path as node, i (node.path)}
<a
href={"/d"+fs_encode_path(node.path)}
class="breadcrumb button flat"
use:path_link={{nav: nav, node: node}}
>
{#if node.abuse_type !== undefined}
<i class="icon small">block</i>
{:else if node.is_shared()}
<i class="icon small">share</i>
{/if}
<div class="node_name" class:base={$nav.base_index === i}>
{node.name}
</div>
</a>
{#if $nav.base_index !== i}
<i class="icon">chevron_right</i>
{/if}
{/each}
{#if directory_view === "list"} {#if directory_view === "list"}
<ListView <ListView

View File

@@ -16,17 +16,20 @@ import type { FSNavigator } from "filesystem/FSNavigator";
import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte"; import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte"; import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import DetailsWindow from "filesystem/DetailsWindow.svelte"; import DetailsWindow from "filesystem/DetailsWindow.svelte";
import SearchBar from "filesystem/SearchBar.svelte";
let { let {
nav, nav,
upload_widget, upload_widget,
edit_window, edit_window,
details_window, details_window,
search_bar,
}: { }: {
nav: FSNavigator nav: FSNavigator
upload_widget: FsUploadWidget upload_widget: FsUploadWidget
edit_window: EditWindow edit_window: EditWindow
details_window: DetailsWindow details_window: DetailsWindow
search_bar: SearchBar
} = $props(); } = $props();
let viewer: any = $state() let viewer: any = $state()
@@ -86,7 +89,7 @@ export const seek = (delta: number) => {
<Spinner/> <Spinner/>
</div> </div>
{:else if viewer_type === "dir"} {:else if viewer_type === "dir"}
<FileManager nav={nav} upload_widget={upload_widget} edit_window={edit_window}> <FileManager nav={nav} upload_widget={upload_widget} edit_window={edit_window} search_bar={search_bar}>
<CustomBanner path={$nav.path}/> <CustomBanner path={$nav.path}/>
</FileManager> </FileManager>
{:else if viewer_type === "audio"} {:else if viewer_type === "audio"}

View File

@@ -12,6 +12,7 @@ export type Tab = {
</script> </script>
<script lang="ts"> <script lang="ts">
import { onMount, type Component } from "svelte"; import { onMount, type Component } from "svelte";
import { breadcrumbs_store } from "wrap/BreadcrumbStore";
import { current_page_store } from "wrap/RouterStore"; import { current_page_store } from "wrap/RouterStore";
let { title = $bindable(""), pages = $bindable([]) }: { let { title = $bindable(""), pages = $bindable([]) }: {
@@ -50,6 +51,8 @@ const get_page = () => {
title = current_subpage === null ? current_page.title : current_subpage.title title = current_subpage === null ? current_page.title : current_subpage.title
window.document.title = title+" / Nova" window.document.title = title+" / Nova"
breadcrumbs_store.set(breadcrumbs)
} }
let current_page: Tab = $state(null) let current_page: Tab = $state(null)
@@ -61,6 +64,14 @@ onMount(() => {
}) })
</script> </script>
{#snippet breadcrumbs()}
{current_page.title}
{#if current_subpage !== null}
<i class="icon">chevron_right</i>
{current_subpage.title}
{/if}
{/snippet}
{#if current_page !== null && current_page.hide_frame !== true} {#if current_page !== null && current_page.hide_frame !== true}
<header> <header>
<div class="tab_bar"> <div class="tab_bar">
@@ -106,9 +117,6 @@ onMount(() => {
{/if} {/if}
<style> <style>
header {
margin-top: 2em;
}
.submenu { .submenu {
border-bottom: 1px solid var(--separator); border-bottom: 1px solid var(--separator);
} }

View File

@@ -0,0 +1,4 @@
import type { Snippet } from "svelte";
import { writable } from "svelte/store";
export let breadcrumbs_store = writable(null as Snippet);

View File

@@ -17,6 +17,7 @@ import { get_user } from "lib/PixeldrainAPI";
import Tree from "./Tree.svelte"; import Tree from "./Tree.svelte";
import MenuEntry from "./MenuEntry.svelte"; import MenuEntry from "./MenuEntry.svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { breadcrumbs_store } from "./BreadcrumbStore";
// The menu swipe will be detected if it was less than this much pixels from the // The menu swipe will be detected if it was less than this much pixels from the
// screen edge // screen edge
@@ -147,16 +148,16 @@ const set_offset = (off: number) => {
<svelte:window ontouchstart={touchstart} ontouchmove={touchmove} ontouchend={touchend}/> <svelte:window ontouchstart={touchstart} ontouchmove={touchmove} ontouchend={touchend}/>
<button class="menu_button" onclick={toggle_menu}> <div class="wrap">
<i class="icon">menu</i> <div class="nav_container" bind:this={menu}>
{#if $menu_is_open}
<span>Menu</span>
{/if}
</button>
<div class="nav_container" bind:this={menu}>
<div class="scroll_container"> <div class="scroll_container">
<nav class="nav" bind:this={nav}> <nav class="nav" bind:this={nav}>
{#if document.documentElement.clientWidth < min_screen_size_menu_open}
<button class="button" onclick={toggle_menu}>
<i class="icon">menu</i>
Menu
</button>
{/if}
<a class="button" href="/" use:highlight_current_page> <a class="button" href="/" use:highlight_current_page>
<i class="icon">home</i> <i class="icon">home</i>
<span>Home</span> <span>Home</span>
@@ -226,21 +227,35 @@ const set_offset = (off: number) => {
<Tree menu_collapsed={false}/> <Tree menu_collapsed={false}/>
</nav> </nav>
</div> </div>
</div> </div>
<div class="page">
<div class="header">
<button class="menu_button" onclick={toggle_menu}>
<i class="icon">menu</i>
</button>
<div class="breadcrumbs">
{#if $breadcrumbs_store !== null}
{@render $breadcrumbs_store()}
{/if}
</div>
</div>
<div class="page">
<Router/> <Router/>
</div> </div>
{#if $loading_store !== 0} {#if $loading_store !== 0}
<div class="spinner"> <div class="spinner">
<Spinner/> <Spinner/>
</div> </div>
{/if} {/if}
</div>
<style> <style>
:global(body) { .wrap {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -251,27 +266,13 @@ const set_offset = (off: number) => {
background-position: var(--background_image_position, initial); background-position: var(--background_image_position, initial);
background-repeat: var(--background_image_repeat, repeat); background-repeat: var(--background_image_repeat, repeat);
background-attachment: fixed; background-attachment: fixed;
width: 100%;
min-height: 100vh;
} }
.menu_button {
position: fixed;
backface-visibility: hidden;
z-index: 10;
top: 0;
left: 0;
background: none;
box-shadow: none;
margin: 0;
padding: 4px;
border-radius: 0;
border-bottom-right-radius: 4px;
backdrop-filter: blur(6px);
}
.nav_container { .nav_container {
flex: 0 0 auto; flex: 0 0 auto;
border-right: 1px solid var(--separator); border-right: 1px solid var(--separator);
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(6px);
z-index: 9; z-index: 9;
} }
.scroll_container { .scroll_container {
@@ -286,7 +287,6 @@ const set_offset = (off: number) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 15em; width: 15em;
padding-top: 2em;
} }
.nav > .button { .nav > .button {
background: none; background: none;
@@ -294,8 +294,10 @@ const set_offset = (off: number) => {
} }
.page { .page {
flex: 1 1 auto; flex: 1 1 auto;
overflow-x: hidden; max-width: 100vw;
max-width: 100%; min-width: 0; /* prevents overflow */
display: flex;
flex-direction: column;
} }
.username { .username {
flex: 1 1 auto; flex: 1 1 auto;
@@ -314,6 +316,28 @@ const set_offset = (off: number) => {
margin: 5px; margin: 5px;
} }
.header {
flex: 0 0 auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: left;
background: var(--shaded_background);
border-bottom: 1px solid var(--separator);
}
.menu_button {
flex: 0 0 auto;
background: none;
box-shadow: none;
}
.breadcrumbs {
flex: 1 1 auto;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: center;
}
.spinner { .spinner {
position: fixed; position: fixed;
top: 10px; top: 10px;

View File

@@ -11,6 +11,7 @@ import Appearance from "pages/Appearance.svelte";
import Footer from "layout/Footer.svelte"; import Footer from "layout/Footer.svelte";
import { current_page_store, type Tab } from "./RouterStore"; import { current_page_store, type Tab } from "./RouterStore";
import { get_user, type User } from "lib/PixeldrainAPI"; import { get_user, type User } from "lib/PixeldrainAPI";
import { breadcrumbs_store } from "./BreadcrumbStore";
let pages: Tab[] = [ let pages: Tab[] = [
{ {
@@ -37,6 +38,7 @@ let pages: Tab[] = [
title: "Filesystem", title: "Filesystem",
component: Filesystem, component: Filesystem,
footer: false, footer: false,
custom_breadcrumbs: true,
}, { }, {
path: "/admin", path: "/admin",
prefix: "/admin/", prefix: "/admin/",
@@ -107,6 +109,10 @@ const load_page = (pathname: string, history: boolean): boolean => {
window.history.pushState({}, window.document.title, pathname) window.history.pushState({}, window.document.title, pathname)
} }
if (current_page.custom_breadcrumbs !== true) {
breadcrumbs_store.set(breadcrumbs)
}
// The current_page_store updates all the listening pages for navigation // The current_page_store updates all the listening pages for navigation
// events. We first wait for a tick so that the current page gets unmounted // events. We first wait for a tick so that the current page gets unmounted
// before sending the event. That way a stale page will not get events which // before sending the event. That way a stale page will not get events which
@@ -144,6 +150,10 @@ const popstate = (e: PopStateEvent) => {
} }
</script> </script>
{#snippet breadcrumbs()}
{current_page.title}
{/snippet}
<svelte:document onclick={click}/> <svelte:document onclick={click}/>
<svelte:window onpopstate={popstate}/> <svelte:window onpopstate={popstate}/>

View File

@@ -8,5 +8,6 @@ export type Tab = {
component?: Component, component?: Component,
footer?: boolean, footer?: boolean,
login?: boolean, login?: boolean,
custom_breadcrumbs?: boolean,
}; };
export let current_page_store = writable({} as Tab); export let current_page_store = writable({} as Tab);