Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions webui/app/src/lib/components/SiteFooter.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import { page } from '$app/stores';
import { Button } from '@hister/components/ui/button';
import { toggleMode, mode } from 'mode-watcher';
import { Sun, Moon, Keyboard } from 'lucide-svelte';
import { showHelp } from '$lib/stores';

const links = [
{ label: 'Help', href: 'help' },
{ label: 'Extractors', href: 'extractors' },
{ label: 'About', href: 'about' },
{ label: 'API', href: 'api-docs' },
{ label: 'GitHub', href: 'https://github.com/asciimoo/hister/', external: true },
];

const iconBtn =
'text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110';
const linkCls =
'font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]';
</script>

<footer
class="bg-brutal-bg border-brutal-border grid h-12 shrink-0 grid-cols-[1fr_auto_1fr] items-center border-t-[3px] px-6 text-sm"
>
<span></span>

<nav class="flex items-center gap-4 md:gap-6" aria-label="Secondary">
{#each links as link (link.href)}
<a
href={link.href}
class={linkCls}
target={link.external ? '_blank' : undefined}
rel={link.external ? 'noopener' : undefined}>{link.label}</a
>
{/each}
</nav>

<div class="flex items-center justify-end gap-1">
<Button variant="ghost" size="icon" class={iconBtn} title="Toggle theme" onclick={toggleMode}>
{#if mode.current === 'dark'}<Sun class="size-5" />{:else}<Moon class="size-5" />{/if}
</Button>
{#if $page.url.pathname === '/'}
<Button
variant="ghost"
size="icon"
class={iconBtn}
title="Keyboard shortcuts (?)"
aria-label="Show keyboard shortcuts"
onclick={() => ($showHelp = !$showHelp)}
>
<Keyboard class="size-5" />
</Button>
{/if}
</div>
</footer>
81 changes: 81 additions & 0 deletions webui/app/src/lib/components/SiteHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script lang="ts">
import { page } from '$app/stores';
import { Button } from '@hister/components/ui/button';
import { LogIn, LogOut, UserRound } from 'lucide-svelte';
import type { AppConfig } from '$lib/api';

let { config, onLogout }: { config: AppConfig | null; onLogout: () => void } = $props();

const navItems = [
{ label: 'History', href: 'history' },
{ label: 'Rules', href: 'rules' },
{ label: 'Add', href: 'add' },
];

const iconBtn =
'text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110 md:size-10';
const navLink =
'font-space p-3 text-[11px] font-semibold tracking-[1px] uppercase no-underline hover:underline md:p-6 md:text-[13px] md:tracking-[1.5px]';
</script>

<header
class="bg-brutal-bg border-brutal-border sticky top-0 z-50 flex h-12 shrink-0 items-center justify-between gap-2 overflow-hidden border-b-[3px] px-3 md:grid md:h-16 md:grid-cols-[4rem_auto_4rem] md:justify-stretch md:gap-4 md:px-6"
>
<h1 class="flex shrink-0 items-center gap-1.5 md:gap-2">
<img src="static/logo.png" alt="Hister logo" class="h-6 w-6 md:h-8 md:w-8" />
<a
data-sveltekit-reload
href="./"
class="font-space text-text-brand text-lg font-extrabold tracking-[1px] uppercase no-underline hover:underline md:text-[28px] md:tracking-[2px]"
>
Hister
</a>
</h1>

<nav class="flex items-center justify-self-center">
{#each navItems as item (item.href)}
{@const active = $page.url.pathname === new URL(item.href, $page.url).pathname}
<a
class="{navLink} {active
? 'text-text-brand font-bold'
: 'text-text-brand-secondary hover:text-text-brand'}"
href={item.href}>{item.label}</a
>
{/each}
</nav>

<div class="flex items-center justify-self-end">
{#if config?.authMode === 'user'}
{#if config?.username}
<Button
variant="ghost"
size="icon"
class={iconBtn}
title="Profile"
onclick={() => (window.location.href = '/profile')}
>
<UserRound class="size-5" />
</Button>
<Button
variant="ghost"
size="icon"
class={iconBtn}
title="Logout {config.username}"
onclick={onLogout}
>
<LogOut class="size-5" />
</Button>
{:else}
<Button
variant="ghost"
size="icon"
class={iconBtn}
title="Login"
onclick={() => (window.location.href = '/auth')}
>
<LogIn class="size-5" />
</Button>
{/if}
{/if}
</div>
</header>
2 changes: 2 additions & 0 deletions webui/app/src/lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default as StatusMessage } from './StatusMessage.svelte';
export { default as SiteHeader } from './SiteHeader.svelte';
export { default as SiteFooter } from './SiteFooter.svelte';
135 changes: 6 additions & 129 deletions webui/app/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script lang="ts">
import { page } from '$app/stores';
import { ModeWatcher, toggleMode, mode } from 'mode-watcher';
import { Button } from '@hister/components/ui/button';
import { Sun, Moon, LogIn, LogOut, UserRound, Keyboard } from 'lucide-svelte';
import '../style.css';
import { ModeWatcher } from 'mode-watcher';
import SiteHeader from '$lib/components/SiteHeader.svelte';
import SiteFooter from '$lib/components/SiteFooter.svelte';
import { fetchConfig, logout, resetConfig, type AppConfig } from '$lib/api';
import { showHelp } from '$lib/stores';
import '../style.css';

let { children } = $props();

Expand All @@ -23,137 +21,16 @@
config = null;
window.location.href = '/';
}

const navItems = [
{ label: 'History', href: 'history' },
{ label: 'Rules', href: 'rules' },
{ label: 'Add', href: 'add' },
];
</script>

<ModeWatcher />

<div class="flex h-dvh flex-col overflow-hidden">
<header
class="bg-brutal-bg border-brutal-border sticky top-0 z-50 flex h-12 shrink-0 items-center justify-between gap-2 overflow-hidden border-b-[3px] px-3 md:grid md:h-16 md:grid-cols-[4rem_auto_4rem] md:justify-stretch md:gap-4 md:px-6"
>
<h1 class="flex shrink-0 items-center gap-1.5 md:gap-2">
<img src="static/logo.png" alt="Hister logo" class="h-6 w-6 md:h-8 md:w-8" />
<a
data-sveltekit-reload
href="./"
class="font-space text-text-brand text-lg font-extrabold tracking-[1px] uppercase no-underline hover:underline md:text-[28px] md:tracking-[2px]"
>
Hister
</a>
</h1>
<nav class="flex items-center justify-self-center">
{#each navItems as item (item.href)}
<a
class="font-space p-3 text-[11px] font-semibold tracking-[1px] uppercase no-underline hover:underline md:p-6 md:text-[13px] md:tracking-[1.5px] {$page
.url.pathname === new URL(item.href, $page.url).pathname
? 'text-text-brand font-bold'
: 'text-text-brand-secondary hover:text-text-brand'}"
href={item.href}
>
{item.label}
</a>
{/each}
</nav>
<div class="flex items-center justify-self-end">
{#if config?.authMode === 'user'}
{#if config?.username}
<Button
variant="ghost"
size="icon"
class="text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110 md:size-10"
title="Profile"
onclick={() => (window.location.href = '/profile')}
>
<UserRound class="size-5" />
</Button>
<Button
variant="ghost"
size="icon"
class="text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110 md:size-10"
title="Logout {config.username}"
onclick={handleLogout}
>
<LogOut class="size-5" />
</Button>
{:else}
<Button
variant="ghost"
size="icon"
class="text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110 md:size-10"
title="Login"
onclick={() => (window.location.href = '/auth')}
>
<LogIn class="size-5" />
</Button>
{/if}
{/if}
</div>
</header>
<SiteHeader {config} onLogout={handleLogout} />

<main class="flex min-h-0 flex-1 flex-col overflow-clip">
{@render children()}
</main>

<footer
class="bg-brutal-bg border-brutal-border grid h-12 shrink-0 grid-cols-[1fr_auto_1fr] items-center border-t-[3px] px-6 text-sm"
>
<span></span>
<div class="flex items-center gap-4 md:gap-6">
<a
href="help"
class="font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]"
>Help</a
>
<a
href="extractors"
class="font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]"
>Extractors</a
>
<a
href="about"
class="font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]"
>About</a
>
<a
href="api-docs"
class="font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]"
>API</a
>
<a
href="https://github.com/asciimoo/hister/"
class="font-space text-text-brand-secondary hover:text-hister-indigo text-[11px] tracking-[1px] uppercase no-underline hover:underline md:text-[13px]"
target="_blank"
rel="noopener">GitHub</a
>
</div>
<div class="flex items-center justify-end gap-1">
<Button
variant="ghost"
size="icon"
class="text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110"
title="Toggle theme"
onclick={toggleMode}
>
{#if mode.current === 'dark'}<Sun class="size-5" />{:else}<Moon class="size-5" />{/if}
</Button>
{#if $page.url.pathname === '/'}
<Button
variant="ghost"
size="icon"
class="text-text-brand-muted hover:text-hister-indigo size-8 shrink-0 transition-all hover:scale-110"
title="Keyboard shortcuts (?)"
aria-label="Show keyboard shortcuts"
onclick={() => ($showHelp = !$showHelp)}
>
<Keyboard class="size-5" />
</Button>
{/if}
</div>
</footer>
<SiteFooter />
</div>