feat: UI WIP
This commit is contained in:
parent
ad9e97b32d
commit
5468fd03e3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { Trash2, Pencil } from '@lucide/svelte';
|
import { Trash2, Pencil, X } from '@lucide/svelte';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { ChatSidebarConversationItem, DialogConfirmation } from '$lib/components/app';
|
import { ChatSidebarConversationItem, DialogConfirmation } from '$lib/components/app';
|
||||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||||
import Label from '$lib/components/ui/label/label.svelte';
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
|
|
@ -140,6 +141,7 @@
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMobileSidebarItemClick();
|
||||||
await goto(`#/chat/${id}`);
|
await goto(`#/chat/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,13 +151,25 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
<ScrollArea class="flex-1">
|
<ScrollArea class="h-full flex-1">
|
||||||
<Sidebar.Header
|
<Sidebar.Header
|
||||||
class=" top-0 z-10 gap-4 bg-sidebar/50 p-3 pt-4 pb-2 backdrop-blur-lg md:sticky"
|
class=" sticky top-0 z-10 gap-4 bg-sidebar/50 p-3 backdrop-blur-lg md:pt-4 md:pb-2"
|
||||||
>
|
>
|
||||||
<a href="#/" onclick={handleMobileSidebarItemClick}>
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
<a href="#/" onclick={handleMobileSidebarItemClick}>
|
||||||
</a>
|
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
class="rounded-full md:hidden"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onclick={() => sidebar.toggle()}
|
||||||
|
>
|
||||||
|
<X class="h-4 w-4" />
|
||||||
|
<span class="sr-only">Close sidebar</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ChatSidebarActions
|
<ChatSidebarActions
|
||||||
bind:this={chatSidebarActions}
|
bind:this={chatSidebarActions}
|
||||||
|
|
@ -209,11 +223,11 @@
|
||||||
</Sidebar.Menu>
|
</Sidebar.Menu>
|
||||||
</Sidebar.GroupContent>
|
</Sidebar.GroupContent>
|
||||||
</Sidebar.Group>
|
</Sidebar.Group>
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
<Sidebar.Footer>
|
<Sidebar.Footer class="sticky bottom-0 z-10 mt-2 bg-sidebar/50 p-3 backdrop-blur-lg">
|
||||||
<ChatSidebarFooter />
|
<ChatSidebarFooter />
|
||||||
</Sidebar.Footer>
|
</Sidebar.Footer>
|
||||||
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogConfirmation
|
<DialogConfirmation
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if capabilities || transportType}
|
{#if capabilities || transportType}
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
{#if transportType}
|
{#if transportType}
|
||||||
{@const TransportIcon = MCP_TRANSPORT_ICONS[transportType]}
|
{@const TransportIcon = MCP_TRANSPORT_ICONS[transportType]}
|
||||||
<Badge variant="outline" class="h-5 gap-1 px-1.5 text-[10px]">
|
<Badge variant="outline" class="h-5 gap-1 px-1.5 text-[10px]">
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@
|
||||||
: 'text-muted-foreground',
|
: 'text-muted-foreground',
|
||||||
isOpen ? 'text-foreground' : ''
|
isOpen ? 'text-foreground' : ''
|
||||||
)}
|
)}
|
||||||
style="max-width: min(calc(100cqw - 9rem), 24rem)"
|
style="max-width: min(calc(100cqw - 9rem), 25rem)"
|
||||||
disabled={disabled || updating}
|
disabled={disabled || updating}
|
||||||
>
|
>
|
||||||
<Package class="h-3.5 w-3.5" />
|
<Package class="h-3.5 w-3.5" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Component } from 'svelte';
|
||||||
import { Download, Upload, Trash2, Database } from '@lucide/svelte';
|
import { Download, Upload, Trash2, Database } from '@lucide/svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { DialogConversationSelection, DialogConfirmation } from '$lib/components/app';
|
import { DialogConversationSelection, DialogConfirmation } from '$lib/components/app';
|
||||||
|
|
@ -8,6 +9,14 @@
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
type SectionOpts = {
|
||||||
|
wrapperClass?: string;
|
||||||
|
titleClass?: string;
|
||||||
|
buttonVariant?: 'outline' | 'destructive';
|
||||||
|
buttonClass?: string;
|
||||||
|
summary?: { show: boolean; verb: string; items: DatabaseConversation[] };
|
||||||
|
};
|
||||||
|
|
||||||
let exportedConversations = $state<DatabaseConversation[]>([]);
|
let exportedConversations = $state<DatabaseConversation[]>([]);
|
||||||
let importedConversations = $state<DatabaseConversation[]>([]);
|
let importedConversations = $state<DatabaseConversation[]>([]);
|
||||||
let showExportSummary = $state(false);
|
let showExportSummary = $state(false);
|
||||||
|
|
@ -175,6 +184,54 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet summaryList(show: boolean, verb: string, items: DatabaseConversation[])}
|
||||||
|
{#if show && items.length > 0}
|
||||||
|
<div class="mt-4 grid overflow-x-auto rounded-lg border border-border/50 bg-muted/30 p-4">
|
||||||
|
<h5 class="mb-2 text-sm font-medium">
|
||||||
|
{verb}
|
||||||
|
{items.length} conversation{items.length === 1 ? '' : 's'}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<ul class="space-y-1 text-sm text-muted-foreground">
|
||||||
|
{#each items.slice(0, 10) as conv (conv.id)}
|
||||||
|
<li class="truncate">• {conv.name || 'Untitled conversation'}</li>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if items.length > 10}
|
||||||
|
<li class="italic">... and {items.length - 10} more</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet section(
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
Icon: Component,
|
||||||
|
buttonText: string,
|
||||||
|
onclick: () => void,
|
||||||
|
opts: SectionOpts
|
||||||
|
)}
|
||||||
|
{@const buttonClass = opts?.buttonClass ?? 'justify-start justify-self-start md:w-auto'}
|
||||||
|
{@const buttonVariant = opts?.buttonVariant ?? 'outline'}
|
||||||
|
<div class="grid gap-1 {opts?.wrapperClass ?? ''}">
|
||||||
|
<h4 class="mt-0 mb-2 text-sm font-medium {opts?.titleClass ?? ''}">{title}</h4>
|
||||||
|
|
||||||
|
<p class="mb-4 text-sm text-muted-foreground">{description}</p>
|
||||||
|
|
||||||
|
<Button class={buttonClass} {onclick} variant={buttonVariant}>
|
||||||
|
<Icon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{#if opts?.summary}
|
||||||
|
{@render summaryList(opts.summary.show, opts.summary.verb, opts.summary.items)}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
<div class="space-y-6" in:fade={{ duration: 150 }}>
|
<div class="space-y-6" in:fade={{ duration: 150 }}>
|
||||||
<div class="flex items-center gap-2 pb-4">
|
<div class="flex items-center gap-2 pb-4">
|
||||||
<Database class="h-5 w-5 md:h-6 md:w-6" />
|
<Database class="h-5 w-5 md:h-6 md:w-6" />
|
||||||
|
|
@ -182,106 +239,42 @@
|
||||||
<h1 class="text-xl font-semibold md:text-2xl">Import / Export</h1>
|
<h1 class="text-xl font-semibold md:text-2xl">Import / Export</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-6">
|
||||||
<div class="grid">
|
{@render section(
|
||||||
<h4 class="mt-0 mb-2 text-sm font-medium">Export Conversations</h4>
|
'Export Conversations',
|
||||||
|
'Download all your conversations as a JSON file. This includes all messages, attachments, and conversation history.',
|
||||||
|
Download,
|
||||||
|
'Export conversations',
|
||||||
|
handleExportClick,
|
||||||
|
{ summary: { show: showExportSummary, verb: 'Exported', items: exportedConversations } }
|
||||||
|
)}
|
||||||
|
|
||||||
<p class="mb-4 text-sm text-muted-foreground">
|
{@render section(
|
||||||
Download all your conversations as a JSON file. This includes all messages, attachments, and
|
'Import Conversations',
|
||||||
conversation history.
|
'Import one or more conversations from a previously exported JSON file. This will merge with your existing conversations.',
|
||||||
</p>
|
Upload,
|
||||||
|
'Import conversations',
|
||||||
|
handleImportClick,
|
||||||
|
{
|
||||||
|
wrapperClass: 'border-t border-border/30 pt-6',
|
||||||
|
summary: { show: showImportSummary, verb: 'Imported', items: importedConversations }
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
{@render section(
|
||||||
class="w-full justify-start justify-self-start md:w-auto"
|
'Delete All Conversations',
|
||||||
onclick={handleExportClick}
|
'Permanently delete all conversations and their messages. This action cannot be undone. Consider exporting your conversations first if you want to keep a backup.',
|
||||||
variant="outline"
|
Trash2,
|
||||||
>
|
'Delete all conversations',
|
||||||
<Download class="mr-2 h-4 w-4" />
|
handleDeleteAllClick,
|
||||||
|
{
|
||||||
Export conversations
|
wrapperClass: 'border-t border-border/30 pt-4',
|
||||||
</Button>
|
titleClass: 'text-destructive',
|
||||||
|
buttonVariant: 'destructive',
|
||||||
{#if showExportSummary && exportedConversations.length > 0}
|
buttonClass:
|
||||||
<div class="mt-4 grid overflow-x-auto rounded-lg border border-border/50 bg-muted/30 p-4">
|
'text-destructive-foreground justify-start justify-self-start bg-destructive hover:bg-destructive/80 md:w-auto'
|
||||||
<h5 class="mb-2 text-sm font-medium">
|
}
|
||||||
Exported {exportedConversations.length} conversation{exportedConversations.length === 1
|
)}
|
||||||
? ''
|
|
||||||
: 's'}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
|
||||||
{#each exportedConversations.slice(0, 10) as conv (conv.id)}
|
|
||||||
<li class="truncate">• {conv.name || 'Untitled conversation'}</li>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if exportedConversations.length > 10}
|
|
||||||
<li class="italic">
|
|
||||||
... and {exportedConversations.length - 10} more
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid border-t border-border/30 pt-4">
|
|
||||||
<h4 class="mt-0 mb-2 text-sm font-medium">Import Conversations</h4>
|
|
||||||
|
|
||||||
<p class="mb-4 text-sm text-muted-foreground">
|
|
||||||
Import one or more conversations from a previously exported JSON file. This will merge with
|
|
||||||
your existing conversations.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
class="w-full justify-start justify-self-start md:w-auto"
|
|
||||||
onclick={handleImportClick}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<Upload class="mr-2 h-4 w-4" />
|
|
||||||
Import conversations
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{#if showImportSummary && importedConversations.length > 0}
|
|
||||||
<div class="mt-4 grid overflow-x-auto rounded-lg border border-border/50 bg-muted/30 p-4">
|
|
||||||
<h5 class="mb-2 text-sm font-medium">
|
|
||||||
Imported {importedConversations.length} conversation{importedConversations.length === 1
|
|
||||||
? ''
|
|
||||||
: 's'}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<ul class="space-y-1 text-sm text-muted-foreground">
|
|
||||||
{#each importedConversations.slice(0, 10) as conv (conv.id)}
|
|
||||||
<li class="truncate">• {conv.name || 'Untitled conversation'}</li>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if importedConversations.length > 10}
|
|
||||||
<li class="italic">
|
|
||||||
... and {importedConversations.length - 10} more
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid border-t border-border/30 pt-4">
|
|
||||||
<h4 class="mt-0 mb-2 text-sm font-medium text-destructive">Delete All Conversations</h4>
|
|
||||||
|
|
||||||
<p class="mb-4 text-sm text-muted-foreground">
|
|
||||||
Permanently delete all conversations and their messages. This action cannot be undone.
|
|
||||||
Consider exporting your conversations first if you want to keep a backup.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
class="text-destructive-foreground w-full justify-start justify-self-start bg-destructive hover:bg-destructive/80 md:w-auto"
|
|
||||||
onclick={handleDeleteAllClick}
|
|
||||||
variant="destructive"
|
|
||||||
>
|
|
||||||
<Trash2 class="mr-2 h-4 w-4" />
|
|
||||||
|
|
||||||
Delete all conversations
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div in:fade={{ duration: 150 }}>
|
<div in:fade={{ duration: 150 }} class="max-h-full overflow-auto">
|
||||||
<div class="flex items-center gap-2 p-4 md:absolute md:top-8 md:left-8 md:p-0">
|
<div class="flex items-center gap-2 p-4 md:absolute md:top-8 md:left-8 md:p-0">
|
||||||
<McpLogo class="h-5 w-5 md:h-6 md:w-6" />
|
<McpLogo class="h-5 w-5 md:h-6 md:w-6" />
|
||||||
|
|
||||||
|
|
@ -141,7 +141,10 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if servers.length > 0}
|
{#if servers.length > 0}
|
||||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fill, minmax(28rem, 1fr));">
|
<div
|
||||||
|
class="grid gap-3"
|
||||||
|
style="grid-template-columns: repeat(auto-fill, minmax(min(32rem, calc(100dvw - 2rem)), 1fr));"
|
||||||
|
>
|
||||||
{#each servers as server (server.id)}
|
{#each servers as server (server.id)}
|
||||||
{#if !initialLoadComplete}
|
{#if !initialLoadComplete}
|
||||||
<McpServerCardSkeleton />
|
<McpServerCardSkeleton />
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class SidebarState {
|
||||||
};
|
};
|
||||||
|
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
|
this.setOpen(!this.open);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Sheet from '$lib/components/ui/sheet/index.js';
|
|
||||||
import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
|
import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { SIDEBAR_WIDTH_MOBILE } from './constants.js';
|
|
||||||
import { useSidebar } from './context.svelte.js';
|
import { useSidebar } from './context.svelte.js';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -33,29 +31,10 @@
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
{:else if sidebar.isMobile}
|
|
||||||
<Sheet.Root bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}>
|
|
||||||
<Sheet.Content
|
|
||||||
data-sidebar="sidebar"
|
|
||||||
data-slot="sidebar"
|
|
||||||
data-mobile="true"
|
|
||||||
class="z-99999 w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground sm:z-99 [&>button]:hidden"
|
|
||||||
style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
|
|
||||||
{side}
|
|
||||||
>
|
|
||||||
<Sheet.Header class="sr-only">
|
|
||||||
<Sheet.Title>Sidebar</Sheet.Title>
|
|
||||||
<Sheet.Description>Displays the mobile sidebar.</Sheet.Description>
|
|
||||||
</Sheet.Header>
|
|
||||||
<div class="flex h-full w-full flex-col">
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
</Sheet.Content>
|
|
||||||
</Sheet.Root>
|
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class="group peer hidden text-sidebar-foreground md:block"
|
class="group peer block text-sidebar-foreground"
|
||||||
data-state={sidebar.state}
|
data-state={sidebar.state}
|
||||||
data-collapsible={sidebar.state === 'collapsed' ? collapsible : ''}
|
data-collapsible={sidebar.state === 'collapsed' ? collapsible : ''}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
|
|
@ -67,8 +46,11 @@
|
||||||
data-slot="sidebar-gap"
|
data-slot="sidebar-gap"
|
||||||
class={cn(
|
class={cn(
|
||||||
'relative bg-transparent transition-[width] duration-200 ease-linear',
|
'relative bg-transparent transition-[width] duration-200 ease-linear',
|
||||||
variant === 'floating' ? 'w-[calc(var(--sidebar-width)+0.75rem)]' : 'w-(--sidebar-width)',
|
'w-0',
|
||||||
'group-data-[collapsible=offcanvas]:w-0',
|
variant === 'floating'
|
||||||
|
? 'md:w-[calc(var(--sidebar-width)+0.75rem)]'
|
||||||
|
: 'md:w-(--sidebar-width)',
|
||||||
|
'md:group-data-[collapsible=offcanvas]:w-0',
|
||||||
'group-data-[side=right]:rotate-180',
|
'group-data-[side=right]:rotate-180',
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
||||||
|
|
@ -79,7 +61,7 @@
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-container"
|
data-slot="sidebar-container"
|
||||||
class={cn(
|
class={cn(
|
||||||
'fixed inset-y-0 z-999 hidden w-(--sidebar-width) duration-200 ease-linear md:z-0 md:flex',
|
'fixed inset-y-0 z-[900] flex w-[calc(100dvw-1.5rem)] duration-200 ease-linear md:z-0 md:w-(--sidebar-width)',
|
||||||
variant === 'floating'
|
variant === 'floating'
|
||||||
? [
|
? [
|
||||||
'transition-[left,right,width,opacity]',
|
'transition-[left,right,width,opacity]',
|
||||||
|
|
|
||||||
|
|
@ -248,8 +248,8 @@
|
||||||
{#if !(alwaysShowSidebarOnDesktop && isDesktop) && !(isSettingsRoute && !isDesktop)}
|
{#if !(alwaysShowSidebarOnDesktop && isDesktop) && !(isSettingsRoute && !isDesktop)}
|
||||||
<Sidebar.Trigger
|
<Sidebar.Trigger
|
||||||
class="transition-left absolute left-0 z-[900] duration-200 ease-linear {sidebarOpen
|
class="transition-left absolute left-0 z-[900] duration-200 ease-linear {sidebarOpen
|
||||||
? 'md:left-[calc(var(--sidebar-width)+0.75rem)]'
|
? 'left-[calc(var(--sidebar-width)+0.75rem)] max-md:hidden'
|
||||||
: 'md:left-0!'}"
|
: 'left-0!'}"
|
||||||
style="translate: 1rem 1rem;"
|
style="translate: 1rem 1rem;"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,23 @@
|
||||||
import { X } from '@lucide/svelte';
|
import { X } from '@lucide/svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { ActionIcon } from '$lib/components/app';
|
import { ActionIcon } from '$lib/components/app';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
|
let previousRouteId = $state<string | null>(null);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const currentId = page.route.id;
|
||||||
|
return () => {
|
||||||
|
previousRouteId = currentId;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
if (browser && window.history.length > 1) {
|
const prevIsSettings = previousRouteId?.startsWith('/settings');
|
||||||
|
if (browser && window.history.length > 1 && !prevIsSettings) {
|
||||||
history.back();
|
history.back();
|
||||||
} else {
|
} else {
|
||||||
goto('#/');
|
goto('#/');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue