Styling and usability fixes

This commit is contained in:
2026-04-08 19:54:09 +02:00
parent 2fb0947077
commit 6d0897e756
13 changed files with 140 additions and 109 deletions

View File

@@ -188,18 +188,20 @@ onMount(() => {
<section> <section>
<h3>Process stats</h3> <h3>Process stats</h3>
<table> <div class="table_scroll">
<tbody> <table>
<tr> <tbody>
<td>DB Time</td> <tr>
<td>{formatDate(new Date(status.db_time), true, true, true)}</td> <td>DB Time</td>
<td>DB Latency</td> <td>{formatDate(new Date(status.db_time), true, true, true)}</td>
<td>{formatNumber(status.db_latency / 1000, 3)} ms</td> <td>DB Latency</td>
<td>PID</td> <td>{formatNumber(status.db_latency / 1000, 3)} ms</td>
<td>{status.pid}</td> <td>PID</td>
</tr> <td>{status.pid}</td>
</tbody> </tr>
</table> </tbody>
</table>
</div>
<h3>Pixelstore stats</h3> <h3>Pixelstore stats</h3>
<div class="table_scroll"> <div class="table_scroll">

View File

@@ -193,7 +193,7 @@ run(() => {
<td>{formatThousands(total_downloads)} (unique, counted once per IP)</td> <td>{formatThousands(total_downloads)} (unique, counted once per IP)</td>
</tr> </tr>
<tr> <tr>
<td>Egress bandwidth</td> <td>Egress traffic</td>
<td> <td>
{formatDataVolume(total_egress, 4)} {formatDataVolume(total_egress, 4)}
( {formatThousands(total_egress)} B ), ( {formatThousands(total_egress)} B ),

View File

@@ -4,7 +4,7 @@ import hsl2rgb from "pure-color/convert/hsl2rgb";
import rgb2hex from "pure-color/convert/rgb2hex"; import rgb2hex from "pure-color/convert/rgb2hex";
import type { FSNode, FSNodeProperties } from "lib/FilesystemAPI.svelte"; import type { FSNode, FSNodeProperties } from "lib/FilesystemAPI.svelte";
const text_contrast = 75 const text_contrast = 80
const body_alpha = 0.9 const body_alpha = 0.9
type Style = { type Style = {
@@ -116,9 +116,9 @@ const add_styles = (style: Style, properties: FSNodeProperties) => {
const add_contrast = (color: string, amt: number) => { const add_contrast = (color: string, amt: number) => {
let hsl = rgb2hsl(parse(color)) // Convert hex to hsl let hsl = rgb2hsl(parse(color)) // Convert hex to hsl
// If the lightness is less than 30 it is considered a dark colour. This // If the lightness is less than 40 it is considered a dark colour. This
// threshold is 30 instead of 50 because overall dark text is more legible // threshold is 40 instead of 50 because overall dark text is more legible
if (hsl[2] < 30) { if (hsl[2] < 40) {
hsl[2] = hsl[2] + amt // Dark color, add lightness hsl[2] = hsl[2] + amt // Dark color, add lightness
} else { } else {
hsl[2] = hsl[2] - amt // Light color, remove lightness hsl[2] = hsl[2] - amt // Light color, remove lightness

View File

@@ -163,7 +163,7 @@ const save = async (e: SubmitEvent) => {
<style> <style>
.tab_bar { .tab_bar {
border-bottom: 2px solid var(--separator); border-bottom: 1px solid var(--separator);
} }
.tab_content { .tab_content {
padding: 8px; padding: 8px;

View File

@@ -86,6 +86,30 @@ const themes = [
brand_background_color: "#1e1e2e", brand_background_color: "#1e1e2e",
brand_body_color: "#181825", brand_body_color: "#181825",
brand_card_color: "#313244", brand_card_color: "#313244",
}, {
name: "Nova",
brand_input_color: "#170025",
brand_highlight_color: "#57e389",
brand_danger_color: "#ed333b",
brand_background_color: "#0d0015",
brand_body_color: "#130020",
brand_card_color: "#1a002b",
}, {
name: "Green Phosphor",
brand_input_color: "#001e09",
brand_highlight_color: "#57e389",
brand_danger_color: "#ed333b",
brand_background_color: "#000502",
brand_body_color: "#000903",
brand_card_color: "#001707",
}, {
name: "Amber",
brand_input_color: "#3c2b00",
brand_highlight_color: "#f5c211",
brand_danger_color: "#c01c28",
brand_background_color: "#171100",
brand_body_color: "#201700",
brand_card_color: "#281d00",
} }
] ]
</script> </script>

View File

@@ -92,7 +92,7 @@ onMount(() => {
<h2>Tracklist</h2> <h2>Tracklist</h2>
{#each siblings as sibling (sibling.path)} {#each siblings as sibling (sibling.path)}
<a href={"/d"+fs_encode_path(sibling.path)} class="node"> <a href={"/d"+fs_encode_path(sibling.path)} class="node" class:playing={sibling.path === $nav.base.path}>
{#if sibling.path === $nav.base.path} {#if sibling.path === $nav.base.path}
<i class="play_arrow icon">play_arrow</i> <i class="play_arrow icon">play_arrow</i>
{:else} {:else}
@@ -131,4 +131,7 @@ onMount(() => {
.play_arrow { .play_arrow {
margin: 4px; margin: 4px;
} }
.playing {
color: var(--highlight_color);
}
</style> </style>

View File

@@ -24,45 +24,45 @@ export const payment_providers: PaymentProvider[] = [
name: "paypal", name: "paypal",
label: "PayPal", label: "PayPal",
}, { }, {
// icon: "bancontact", icon: "bancontact",
// name: "bancontact", name: "bancontact",
// label: "Bancontact", label: "Bancontact",
// need_name: true, need_name: true,
// country_filter: ["BE"], country_filter: ["BE"],
// }, { }, {
// icon: "eps", icon: "eps",
// name: "eps", name: "eps",
// label: "EPS", label: "EPS",
// need_name: true, need_name: true,
// country_filter: ["AT"], country_filter: ["AT"],
// }, { }, {
// icon: "ideal", icon: "ideal",
// name: "ideal", name: "ideal",
// label: "iDEAL", label: "iDEAL",
// need_name: true, need_name: true,
// country_filter: ["NL"], country_filter: ["NL"],
// }, { }, {
// icon: "p24", icon: "p24",
// name: "p24", name: "p24",
// label: "Przelewy24", label: "Przelewy24",
// need_name: true, need_name: true,
// country_filter: ["PL"], country_filter: ["PL"],
// }, { }, {
// icon: "trustly", icon: "trustly",
// name: "trustly", name: "trustly",
// label: "Trustly", label: "Trustly",
// need_name: true, need_name: true,
// country_filter: ["AT", "DE", "DK", "EE", "ES", "FI", "GB", "LT", "LV", "NL", "NO", "SE"] country_filter: ["AT", "DE", "DK", "EE", "ES", "FI", "GB", "LT", "LV", "NL", "NO", "SE"]
// }, { }, {
icon: "bitcoin", icon: "bitcoin",
name: "btc", name: "btc",
label: "Bitcoin", label: "Bitcoin",
crypto: true, crypto: true,
// }, { }, {
// icon: "dogecoin", icon: "dogecoin",
// name: "doge", name: "doge",
// label: "Dogecoin", label: "Dogecoin",
// crypto: true, crypto: true,
}, { }, {
icon: "monero", icon: "monero",
name: "xmr", name: "xmr",

View File

@@ -364,7 +364,7 @@ export const fs_node_type = (node: FSNode) => {
export const fs_node_icon = (node: FSNode, width = 64, height = 64) => { export const fs_node_icon = (node: FSNode, width = 64, height = 64) => {
if (node.type === "dir") { if (node.type === "dir") {
// Folders with an ID are publically shared, use the shared folder icon // Folders with an ID are publicly shared, use the shared folder icon
if (node.is_shared()) { if (node.is_shared()) {
return "/res/img/mime/folder-remote.png" return "/res/img/mime/folder-remote.png"
} else { } else {

View File

@@ -5,8 +5,6 @@ let global_index = 10000;
</script> </script>
<script lang="ts"> <script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy';
const bubble = createBubbler();
import { type Snippet } from "svelte"; import { type Snippet } from "svelte";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import Button from "layout/Button.svelte"; import Button from "layout/Button.svelte";
@@ -36,9 +34,11 @@ let {
children?: Snippet, children?: Snippet,
} = $props(); } = $props();
const load_bg = background => { let zindex: number = $state()
background.style.zIndex = global_index.valueOf(); const load_bg = (background: HTMLDivElement) => {
zindex = global_index.valueOf()
global_index++; global_index++;
background.style.zIndex = global_index.valueOf().toString();
} }
export const show = () => { set_visible(true) } export const show = () => { set_visible(true) }
@@ -67,7 +67,7 @@ const keydown = (e: KeyboardEvent) => {
<!-- svelte-ignore a11y_interactive_supports_focus --> <!-- svelte-ignore a11y_interactive_supports_focus -->
<div <div
class="background" class="background"
style={style} style="z-index: {zindex}; {style}"
use:load_bg use:load_bg
onclick={hide} onclick={hide}
transition:fade={{duration: 200}} transition:fade={{duration: 200}}
@@ -78,11 +78,11 @@ const keydown = (e: KeyboardEvent) => {
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div <div
class="window" class="window"
onclick={stopPropagation(bubble('click'))}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
style="width: {width}; height: {height};" style="width: {width}; height: {height};"
onkeydown={keydown} onkeydown={keydown}
onclick={e => e.stopPropagation()}
> >
<div class="header"> <div class="header">
{#if header}{@render header()}{:else} {#if header}{@render header()}{:else}

View File

@@ -1,12 +1,10 @@
<script lang="ts"> <script lang="ts">
import { bookmarks_save, bookmarks_store } from "lib/Bookmarks"; import { bookmarks_save, bookmarks_store } from "lib/Bookmarks";
import { fs_encode_path } from "lib/FilesystemAPI.svelte"; import { fs_encode_path, fs_thumbnail_url } from "lib/FilesystemAPI.svelte";
import { highlight_current_page } from "lib/HighlightCurrentPage"; import { highlight_current_page } from "lib/HighlightCurrentPage";
import MenuEntry from "./MenuEntry.svelte"; import MenuEntry from "./MenuEntry.svelte";
import { flip } from "svelte/animate"; import { flip } from "svelte/animate";
let { menu_collapsed }: { menu_collapsed: boolean } = $props();
let editing = $state(false) let editing = $state(false)
const toggle_edit = async () => { const toggle_edit = async () => {
@@ -76,7 +74,7 @@ const drop = (e: DragEvent, drop_idx: number) => {
</script> </script>
{#if $bookmarks_store.length !== 0} {#if $bookmarks_store.length !== 0}
<MenuEntry id="bookmarks" collapsed={menu_collapsed}> <MenuEntry id="bookmarks">
{#snippet title()} {#snippet title()}
<div class="title">Bookmarks</div> <div class="title">Bookmarks</div>
<button onclick={toggle_edit} class:button_highlight={editing} class="button flat"> <button onclick={toggle_edit} class:button_highlight={editing} class="button flat">
@@ -116,8 +114,8 @@ const drop = (e: DragEvent, drop_idx: number) => {
</button> </button>
{:else} {:else}
<a class="button" href="/d{fs_encode_path(bookmark.path)}" use:highlight_current_page> <a class="button" href="/d{fs_encode_path(bookmark.path)}" use:highlight_current_page>
<i class="icon">{bookmark.icon}</i> <img src={fs_thumbnail_url(bookmark.path, 32, 32)} class="thumbnail" alt="Bookmark icon" />
<span class:hide={menu_collapsed}>{bookmark.label}</span> <span>{bookmark.label}</span>
</a> </a>
{/if} {/if}
</div> </div>
@@ -145,11 +143,13 @@ const drop = (e: DragEvent, drop_idx: number) => {
background: none; background: none;
box-shadow: none; box-shadow: none;
} }
.hide {
display: none;
}
.highlight { .highlight {
box-shadow: 0 0 0px 1px var(--highlight_color); box-shadow: 0 0 0px 1px var(--highlight_color);
text-decoration: none; text-decoration: none;
} }
.thumbnail {
height: 1.5em;
width: 1.5em;
border-radius: 2px;
}
</style> </style>

View File

@@ -21,7 +21,7 @@ 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
const screen_edge_offset = 50 const screen_edge_offset = 40
// Dead zone before the swipe action gets detected // Dead zone before the swipe action gets detected
const swipe_dead_zone = screen_edge_offset/4 const swipe_dead_zone = screen_edge_offset/4
@@ -77,12 +77,12 @@ const touchmove = (e: TouchEvent) => {
const abs_x = Math.abs(x) const abs_x = Math.abs(x)
const abs_y = Math.abs(y) const abs_y = Math.abs(y)
// The cursor must have moved at least swipe_dead_zone pixels and three // The cursor must have moved at least swipe_dead_zone pixels and twice as
// times as much on the x axis than the y axis for it to count as a swipe // much on the x axis than the y axis for it to count as a swipe
if (abs_x > swipe_dead_zone && abs_x / 3 > abs_y) { if (abs_x > swipe_dead_zone && abs_x / 2 > abs_y) {
set_offset(initial_offset+(x*2)) set_offset(initial_offset+(x*2))
} else { } else {
set_offset(initial_offset) touchend(e)
} }
} }
@@ -158,10 +158,25 @@ const set_offset = (off: number) => {
Menu Menu
</button> </button>
{/if} {/if}
<a class="button" href="/" use:highlight_current_page>
<i class="icon">home</i> <MenuEntry id="home" collapsed={false} no_border>
<span>Home</span> {#snippet title()}
</a> <a class="button" style="flex: 1 1 auto;" href="/" use:highlight_current_page>
<span style="flex: 1 1 auto;">Nova.storage</span>
<img src="/res/img/nova_64.png" style="width: 1.5em; height: 1.5em; flex: 0 0 auto;" alt="Nova.storage logo">
</a>
{/snippet}
{#snippet body()}
<a class="button" href="/speedtest" use:highlight_current_page>
<i class="icon">speed</i>
<span>Speedtest</span>
</a>
<a class="button" href="/appearance" use:highlight_current_page>
<i class="icon">palette</i>
<span>Themes</span>
</a>
{/snippet}
</MenuEntry>
{#if $user.username !== undefined && $user.username !== ""} {#if $user.username !== undefined && $user.username !== ""}
<MenuEntry id="subscription_info" collapsed={false}> <MenuEntry id="subscription_info" collapsed={false}>
@@ -212,19 +227,8 @@ const set_offset = (off: number) => {
</a> </a>
{/if} {/if}
<div class="separator"></div> <Bookmarks/>
<Tree/>
<a class="button" href="/speedtest" use:highlight_current_page>
<i class="icon">speed</i>
<span>Speedtest</span>
</a>
<a class="button" href="/appearance" use:highlight_current_page>
<i class="icon">palette</i>
<span>Themes</span>
</a>
<Bookmarks menu_collapsed={false}/>
<Tree menu_collapsed={false}/>
</nav> </nav>
</div> </div>
</div> </div>
@@ -273,6 +277,7 @@ const set_offset = (off: number) => {
flex: 0 0 auto; flex: 0 0 auto;
border-right: 1px solid var(--separator); border-right: 1px solid var(--separator);
background: var(--body_background); background: var(--body_background);
backdrop-filter: blur(3px);
z-index: 9; z-index: 9;
} }
.scroll_container { .scroll_container {
@@ -288,7 +293,7 @@ const set_offset = (off: number) => {
flex-direction: column; flex-direction: column;
width: 15em; width: 15em;
} }
.nav > .button { .button {
background: none; background: none;
box-shadow: none; box-shadow: none;
} }
@@ -304,11 +309,6 @@ const set_offset = (off: number) => {
margin: 3px; margin: 3px;
} }
.separator {
height: 1px;
width: 100%;
background-color: var(--separator);
}
.stats_table { .stats_table {
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;
@@ -324,6 +324,7 @@ const set_offset = (off: number) => {
justify-content: left; justify-content: left;
background: var(--body_background); background: var(--body_background);
border-bottom: 1px solid var(--separator); border-bottom: 1px solid var(--separator);
backdrop-filter: blur(3px);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 8; /* below navigation, on top of most other things */ z-index: 8; /* below navigation, on top of most other things */

View File

@@ -7,11 +7,13 @@ type Expandable = {[key: string]: boolean}
let { let {
id, id,
collapsed = false, collapsed = false,
no_border,
title, title,
body, body,
}: { }: {
id: string id: string
collapsed: boolean collapsed?: boolean
no_border?: boolean
title: import('svelte').Snippet title: import('svelte').Snippet
body: import('svelte').Snippet body: import('svelte').Snippet
} = $props(); } = $props();
@@ -43,8 +45,8 @@ const toggle = (e: MouseEvent) => {
} }
</script> </script>
<div class="title"> <div class="title" class:no_border>
<ToggleButton bind:on={expanded} action={toggle} icon_on="arrow_drop_down" icon_off="arrow_drop_up" highlight={false} flat/> <ToggleButton bind:on={expanded} action={toggle} icon_on="arrow_drop_down" icon_off="arrow_right" highlight={false} flat/>
{#if !collapsed} {#if !collapsed}
{@render title()} {@render title()}
@@ -63,4 +65,7 @@ const toggle = (e: MouseEvent) => {
align-items: center; align-items: center;
border-top: 1px solid var(--separator); border-top: 1px solid var(--separator);
} }
.no_border {
border-top: none;
}
</style> </style>

View File

@@ -5,7 +5,6 @@ import { highlight_current_page } from "lib/HighlightCurrentPage";
import { onMount } from "svelte"; import { onMount } from "svelte";
import MenuEntry from "./MenuEntry.svelte"; import MenuEntry from "./MenuEntry.svelte";
let { menu_collapsed }: { menu_collapsed: boolean } = $props();
let siblings: FSNode[] = $state([]) let siblings: FSNode[] = $state([])
onMount(() => { onMount(() => {
@@ -29,7 +28,7 @@ onMount(() => {
}) })
</script> </script>
<MenuEntry id="tree_parents" collapsed={menu_collapsed}> <MenuEntry id="tree_parents">
{#snippet title()} {#snippet title()}
<div class="title">Parent directories</div> <div class="title">Parent directories</div>
<button title="Navigate up" onclick={() => global_navigator.navigate_up()} class="button flat"> <button title="Navigate up" onclick={() => global_navigator.navigate_up()} class="button flat">
@@ -43,7 +42,7 @@ onMount(() => {
<div class="row"> <div class="row">
<a class="button" href="/d{fs_encode_path(node.path)}" title="{node.name}"> <a class="button" href="/d{fs_encode_path(node.path)}" title="{node.name}">
<img class="thumbnail" src="{fs_node_icon(node, 32, 32)}" alt="{node.name}"/> <img class="thumbnail" src="{fs_node_icon(node, 32, 32)}" alt="{node.name}"/>
<span class:hide={menu_collapsed}>{node.name}</span> <span>{node.name}</span>
</a> </a>
</div> </div>
{/if} {/if}
@@ -51,7 +50,7 @@ onMount(() => {
{/snippet} {/snippet}
</MenuEntry> </MenuEntry>
<MenuEntry id="tree_siblings" collapsed={menu_collapsed}> <MenuEntry id="tree_siblings">
{#snippet title()} {#snippet title()}
<div class="title">Siblings</div> <div class="title">Siblings</div>
<button title="Open previous sibling" onclick={() => global_navigator.open_sibling(-1)} class="button flat"> <button title="Open previous sibling" onclick={() => global_navigator.open_sibling(-1)} class="button flat">
@@ -68,7 +67,7 @@ onMount(() => {
<div class="row"> <div class="row">
<a class="button" href="/d{fs_encode_path(node.path)}" title="{node.name}" use:highlight_current_page> <a class="button" href="/d{fs_encode_path(node.path)}" title="{node.name}" use:highlight_current_page>
<img class="thumbnail" src="{fs_node_icon(node, 32, 32)}" alt="{node.name}"/> <img class="thumbnail" src="{fs_node_icon(node, 32, 32)}" alt="{node.name}"/>
<span class:hide={menu_collapsed}>{node.name}</span> <span>{node.name}</span>
</a> </a>
</div> </div>
{/if} {/if}
@@ -102,7 +101,4 @@ onMount(() => {
width: 1.5em; width: 1.5em;
border-radius: 2px; border-radius: 2px;
} }
.hide {
display: none;
}
</style> </style>