mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 14:28:01 -05:00
WIP: Notes Drawer. Updated default theme to rocket
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
117
web/src/lib/components/ui/noteDrawer/NoteDrawer.svelte
Normal file
117
web/src/lib/components/ui/noteDrawer/NoteDrawer.svelte
Normal 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>
|
||||
37
web/src/lib/store/noteStore.ts
Normal file
37
web/src/lib/store/noteStore.ts
Normal 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();
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user