WIP: Notes Drawer. Updated default theme to rocket

This commit is contained in:
John
2024-12-24 07:40:29 -05:00
parent cbd2ffe81d
commit 8c68ebc0ee
6 changed files with 231 additions and 45 deletions

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="my-custom-theme">
<body data-sveltekit-preload-data="hover" data-theme="rocket">
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body>
</html>
</html>

View File

@@ -4,24 +4,56 @@
import ModelConfig from "./ModelConfig.svelte";
import Models from "./Models.svelte";
import Patterns from "./Patterns.svelte";
import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
import { getDrawerStore } from '@skeletonlabs/skeleton';
import { page } from '$app/stores';
import { beforeNavigate } from '$app/navigation';
const drawerStore = getDrawerStore();
function openDrawer() {
drawerStore.open({});
}
beforeNavigate(() => {
drawerStore.close();
});
$: isVisible = $page.url.pathname.startsWith('/chat');
</script>
<div class="flex-1 mx-auto p-4 min-h-screen">
<div class="grid grid-cols-1 auto-fit lg:grid-cols-[250px_minmax(250px,_1.5fr)_minmax(250px,_1.5fr)] gap-4 h-[calc(100vh-2rem)]">
<div class="flex flex-col space-y-1 order-3 lg:order-1">
<div class="space-y-2 max-w-full">
<div class="flex flex-col gap-2">
<Patterns />
<Models />
<ModelConfig />
</div>
</div>
</div>
<div class="flex flex-col space-y-4 order-2 lg:order-2">
<ChatInput />
</div>
<div class="flex flex-col border rounded-lg bg-muted/50 p-4 order-1 lg:order-3 max-h-[695px]">
<ChatMessages />
</div>
</div>
</div>
<div class="grid grid-cols-1 auto-fit lg:grid-cols-[250px_minmax(250px,_1.5fr)_minmax(250px,_1.5fr)] gap-4 h-[calc(100vh-2rem)]">
<div class="flex flex-col space-y-1 order-3 lg:order-1">
<div class="space-y-2 max-w-full">
<div class="flex flex-col gap-2">
<Patterns />
<Models />
<ModelConfig />
</div>
<div class="flex flex-col gap-2">
{#if isVisible}
<div class="flex justify-start mt-2">
<button type="button"
class="btn btn-sm border variant-filled-primary"
on:click={openDrawer}
>Open Drawer
</button>
</div>
<NoteDrawer />
{/if}
</div>
</div>
<!-- <button class="primary" on:click={openDrawer}>Open Drawer</button> -->
</div>
<div class="flex flex-col space-y-4 order-2 lg:order-2">
<ChatInput />
</div>
<div class="flex flex-col border rounded-lg bg-muted/50 p-4 order-1 lg:order-3 max-h-[695px]">
<ChatMessages />
</div>
</div>
</div>

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { Drawer, getDrawerStore, getToastStore } from '@skeletonlabs/skeleton';
import type { DrawerSettings, DrawerStore } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
import { noteStore } from '$lib/store/noteStore';
import { afterNavigate, beforeNavigate } from '$app/navigation';
const drawerStore = getDrawerStore();
const toastStore = getToastStore();
let textareaEl: HTMLTextAreaElement;
let saving = false;
let content = '';
// Auto-resize textarea
function adjustTextareaHeight() {
if (textareaEl) {
textareaEl.style.height = 'auto';
textareaEl.style.height = textareaEl.scrollHeight + 'px';
}
}
async function saveContent() {
try {
saving = true;
await new Promise(resolve => setTimeout(resolve, 500)); // Create an endpoint here
localStorage.setItem('savedText', $noteStore.content);
noteStore.save();
toastStore.trigger({
message: 'Notes saved successfully!',
background: 'variant-filled-success'
});
} catch (error) {
toastStore.trigger({
message: 'Failed to save notes',
background: 'variant-filled-error'
});
} finally {
saving = false;
}
}
// Prompt user if trying to close with unsaved changes
$: if ($drawerStore.open === false && $noteStore.isDirty) {
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
noteStore.reset();
} else {
drawerStore.open({});
}
}
// Load saved content when drawer opens
$: if ($drawerStore.open) {
const savedContent = localStorage.getItem('savedText');
if (savedContent) {
noteStore.updateContent(savedContent);
noteStore.save();
}
}
// Keyboard shortcuts
function handleKeydown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault();
saveContent();
}
}
onMount(() => {
adjustTextareaHeight();
});
</script>
<Drawer width="w-[40%]" padding="p-4">
{#if $drawerStore.open}
<div class="space-y-4">
<header class="flex justify-between items-center">
<h2 class="m-2 p-1 h2">Notes</h2>
{#if $noteStore.lastSaved}
<span class="text-sm opacity-70">
Last saved: {$noteStore.lastSaved.toLocaleTimeString()}
</span>
{/if}
</header>
<div class="m-4">
<textarea
bind:this={textareaEl}
bind:value={$noteStore.content}
on:input={adjustTextareaHeight}
on:keydown={handleKeydown}
class="w-full min-h-screen p-2 rounded-container-token text-black resize-none"
placeholder="Enter your text here..."
/>
</div>
<footer class="flex justify-between items-center">
<span class="text-sm opacity-70">
{#if $noteStore.isDirty}
Unsaved changes
{/if}
</span>
<button
class="btn variant-filled-primary"
on:click={saveContent}
disabled={saving || !$noteStore.isDirty}
>
{#if saving}
Saving...
{:else}
Save
{/if}
</button>
</footer>
</div>
{/if}
</Drawer>

View File

@@ -0,0 +1,37 @@
import { writable } from 'svelte/store';
interface NoteState {
content: string;
lastSaved: Date | null;
isDirty: boolean;
}
function createNoteStore() {
const { subscribe, set, update } = writable<NoteState>({
content: '',
lastSaved: null,
isDirty: false
});
return {
subscribe,
updateContent: (content: string) => update(state => ({
...state,
content,
isDirty: true
})),
save: () => update(state => ({
...state,
lastSaved: new Date(),
isDirty: false
})),
reset: () => set({
content: '',
lastSaved: null,
isDirty: false
})
};
}
export const noteStore = createNoteStore();

View File

@@ -2,7 +2,6 @@
import '../app.postcss';
import { AppShell } from '@skeletonlabs/skeleton';
import { Toast } from '@skeletonlabs/skeleton';
//import { getToastStore } from '@skeletonlabs/skeleton';
import ToastContainer from '$lib/components/ui/toast/ToastContainer.svelte';
import Footer from '$lib/components/home/Footer.svelte';
import Header from '$lib/components/home/Header.svelte';
@@ -11,10 +10,11 @@
import { fly } from 'svelte/transition';
import { getToastStore } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
// Initialize stores
import { getDrawerStore } from '@skeletonlabs/skeleton';
// Initialize stores
initializeStores();
const drawerStore = getDrawerStore();
const toastStore = getToastStore();
onMount(() => {
@@ -32,31 +32,31 @@
<ToastContainer />
{#key $page.url.pathname}
<AppShell class="relative">
<div class="fixed inset-0 bg-gradient-to-br from-primary-500/20 via-tertiary-500/20 to-secondary-500/20 -z-10"></div>
<svelte:fragment slot="header">
<Header />
<div class="h-2 py-4">
</svelte:fragment>
<div
in:fly={{ duration: 500, delay: 100, y: 100 }}
>
<AppShell class="relative">
<div class="fixed inset-0 bg-gradient-to-br from-primary-500/20 via-tertiary-500/20 to-secondary-500/20 -z-10"></div>
<svelte:fragment slot="header">
<Header />
<div class="h-2 py-4">
</svelte:fragment>
<div
in:fly={{ duration: 500, delay: 100, y: 100 }}
>
<main class="m-auto p-4">
<slot />
</main>
</div>
<svelte:fragment slot="footer">
<Footer />
</svelte:fragment>
</AppShell>
<svelte:fragment slot="footer">
<Footer />
</svelte:fragment>
</AppShell>
{/key}
<style>
main {
padding: 2rem;
box-sizing: border-box;
overflow-y: auto;
}
main {
padding: 2rem;
box-sizing: border-box;
overflow-y: auto;
}
</style>

View File

@@ -25,7 +25,7 @@
{#if augemented}
<div class="py-2 mb-6" transition:slide|local="{{delay: 250, duration: 3000, easing: quintOut }}">
<div class="py-2" transition:slide|local="{{delay: 250, duration: 3000, easing: quintOut }}">
<h2 class="h2 text-2xl text-center font-extrabold bg-gradient-to-br to-blue-500 from-cyan-300 bg-clip-text text-transparent pb-2">Human Flourishing via AI Augmentation</h2>
<div class="text-2xl">
<p class="mt-2 font-bold">Fill in the blanks... </p>