feat: Add history section

This commit is contained in:
Sidharth Vinod
2025-03-13 13:01:47 -07:00
parent 0f3cfbdeac
commit 0532e403bf
13 changed files with 182 additions and 98 deletions
+15 -15
View File
@@ -1,17 +1,17 @@
{
"$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app.postcss",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks"
},
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
"$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app.postcss",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks"
},
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
}
+1
View File
@@ -69,6 +69,7 @@
"prettier-plugin-tailwindcss": "^0.6.0",
"svelte": "^5.0.0",
"svelte-preprocess": "^6.0.0",
"svelte-sonner": "^0.3.28",
"tailwind-merge": "^3.0.2",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.1",
+3 -1
View File
@@ -33,7 +33,9 @@
<Button
role="tab"
variant="ghost"
class="px-2 {activeTabID === tab.id ? 'rounded-b-none border-b-2 border-b-primary/50' : ''}"
class="px-2 {activeTabID === tab.id
? 'rounded-b-none border-b-2 border-b-primary-foreground/50'
: ''}"
onclick={toggleTabs(tab)}
onkeypress={toggleTabs(tab)}>
<tab.icon />
+67 -59
View File
@@ -1,6 +1,4 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy';
import Card from '$lib/components/Card/Card.svelte';
import type { HistoryEntry, HistoryType, State, Tab } from '$lib/types';
import { notify, prompt } from '$lib/util/notify';
@@ -11,13 +9,15 @@
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import GitAltIcon from '~icons/fa-brands/git-alt';
import BookmarkIcon from '~icons/fa-regular/bookmark';
import SaveIcon from '~icons/fa-regular/save';
import DownloadIcon from '~icons/fa/download';
import UploadIcon from '~icons/fa/upload';
import HistoryIcon from '~icons/fa6-solid/history';
import TrashAltIcon from '~icons/fa6-solid/trash-alt';
import UndoIcon from '~icons/fa6-solid/undo';
import BookmarkIcon from '~icons/material-symbols/bookmark-outline-rounded';
import TrashAltIcon from '~icons/material-symbols/delete-outline-rounded';
import DownloadIcon from '~icons/material-symbols/download-rounded';
import SaveIcon from '~icons/material-symbols/save-outline-rounded';
import UndoIcon from '~icons/material-symbols/settings-backup-restore-rounded';
import UploadIcon from '~icons/material-symbols/upload-rounded';
import HistoryIcon from '~icons/mdi/clock-outline';
import { Button } from '../ui/button';
import { Separator } from '../ui/separator';
import {
addHistoryEntry,
clearHistoryData,
@@ -35,6 +35,7 @@
const tabSelectHandler = (tab: Tab) => {
historyModeStore.set(tab.id as HistoryType);
};
let tabs: Tab[] = $state([
{
id: 'manual',
@@ -102,11 +103,6 @@
inputStateStore.set({ ...state, updateDiagram: true });
};
const relativeTime = (time: number) => {
const t = new Date(time);
return `${new Date(t).toLocaleString()} (${dayjs(t).fromNow()})`;
};
onMount(() => {
historyModeStore.set('manual');
setInterval(() => {
@@ -129,68 +125,80 @@
});
</script>
<Card onselect={tabSelectHandler} isOpen={false} {tabs} title="History">
<Card onselect={tabSelectHandler} isOpen isClosable={false} {tabs}>
{#snippet actions()}
<div>
<button
<div class="flex items-center gap-2">
<Button
size="icon"
variant="ghost"
id="uploadHistory"
class="btn btn-secondary btn-xs w-12"
onclick={stopPropagation(() => uploadHistory())}
title="Upload history"
aria-label="Upload history"><UploadIcon /></button>
onclick={uploadHistory}
title="Upload history"><UploadIcon /></Button>
{#if $historyStore.length > 0}
<button
<Button
id="downloadHistory"
class="btn btn-secondary btn-xs w-12"
onclick={stopPropagation(() => downloadHistory())}
title="Download history"
aria-label="Download history"><DownloadIcon /></button>
size="icon"
variant="ghost"
onclick={downloadHistory}
title="Download history"><DownloadIcon /></Button>
{/if}
|
<button
<Separator orientation="vertical" />
<Button
id="saveHistory"
class="btn btn-success btn-xs w-12"
onclick={stopPropagation(() => saveHistory())}
title="Save current state"
aria-label="Save current state"><SaveIcon /></button>
size="icon"
variant="ghost"
onclick={() => saveHistory()}
title="Save current state"><SaveIcon /></Button>
{#if $historyModeStore !== 'loader'}
<button
<Button
id="clearHistory"
class="btn btn-error btn-xs w-12"
onclick={stopPropagation(() => clearHistory())}
title="Delete all saved states"
aria-label="Delete all saved states"><TrashAltIcon /></button>
size="icon"
variant="ghost"
class="hover:text-destructive"
onclick={() => clearHistory()}
title="Delete all saved states"><TrashAltIcon /></Button>
{/if}
</div>
{/snippet}
<ul class="h-56 space-y-2 overflow-auto p-2" id="historyList">
<ul class="flex h-full min-w-fit flex-col gap-2 overflow-auto p-2" id="historyList">
{#if $historyStore.length > 0}
{#each $historyStore as { id, state, time, name, url, type }}
<li class="flex-col rounded p-2 shadow">
<div class="flex">
<div class="flex-1">
<div class="text-base-content flex flex-col">
{#if url}
<a
href={url}
target="_blank"
title="Open revision in new tab"
class="text-blue-500 hover:underline">{name}</a>
{:else}
<span>{name}</span>
{/if}
<span class="text-sm text-gray-400">{relativeTime(time)}</span>
</div>
{#each $historyStore as { id, state, time, name, url, type } (id)}
<li class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<div class="flex flex-col">
{#if url}
<a
href={url}
target="_blank"
title="Open revision in new tab"
class="text-blue-500 hover:underline">{name}</a>
{:else}
<span>{name}</span>
{/if}
<span class="whitespace-nowrap text-xs text-primary-foreground/30">
{new Date(time).toLocaleString()}
</span>
</div>
<div class="flex content-center gap-2">
<button class="btn btn-success" onclick={() => restoreHistoryItem(state)}
><UndoIcon class="mr-1" />Restore</button>
<div class="flex items-center gap-2">
<span class="whitespace-nowrap text-sm text-primary-foreground/50">
{dayjs(time).fromNow()}
</span>
<Button size="icon" variant="ghost" onclick={() => restoreHistoryItem(state)}>
<UndoIcon />
</Button>
{#if type !== 'loader'}
<button class="btn btn-error" onclick={() => clearHistory(id)}
><TrashAltIcon class="mr-1" />Delete</button>
<Button
size="icon"
variant="ghost"
class="hover:text-destructive"
onclick={() => clearHistory(id)}>
<TrashAltIcon />
</Button>
{/if}
</div>
</div>
<Separator />
</li>
{/each}
{:else}
+5 -5
View File
@@ -1,10 +1,10 @@
import { derived, writable, get } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import { persist, localStorage } from '$lib/util/persist';
import { generateSlug } from 'random-word-slugs';
import type { HistoryEntry, HistoryType, Optional } from '$lib/types';
import { v4 as uuidV4 } from 'uuid';
import { localStorage, persist } from '$lib/util/persist';
import { logEvent } from '$lib/util/stats';
import { generateSlug } from 'random-word-slugs';
import type { Readable, Writable } from 'svelte/store';
import { derived, get, writable } from 'svelte/store';
import { v4 as uuidV4 } from 'uuid';
const MAX_AUTO_HISTORY_LENGTH = 30;
+8 -6
View File
@@ -15,13 +15,19 @@
import { stateStore } from '$lib/util/state';
import { MCBaseURL } from '$lib/util/util';
import { toggleMode } from 'mode-watcher';
import type { ComponentProps } from 'svelte';
import type { ComponentProps, Snippet } from 'svelte';
import GithubIcon from '~icons/fa-brands/github';
import CloseIcon from '~icons/fa/close';
import MoonIcon from '~icons/fa6-solid/moon';
import SunIcon from '~icons/fa6-solid/sun';
import DropdownNavMenu from './DropdownNavMenu.svelte';
interface Props {
children: Snippet;
}
let { children }: Props = $props();
const { isEnabledMermaidChartLinks } = env;
let isMenuOpen = $state(false);
@@ -185,11 +191,7 @@
class="absolute rotate-90 scale-0 transition-all duration-200 dark:rotate-0 dark:scale-100" />
<span class="sr-only">Toggle theme</span>
</Button>
<Button variant="secondary" size="sm">Share</Button>
{#if isEnabledMermaidChartLinks}
<Button size="sm" target="_blank" href="https://mermaidchart.com"
><img class="size-6" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />Save diagram</Button>
{/if}
{@render children()}
</div>
</div>
</nav>
+1
View File
@@ -0,0 +1 @@
export { default as Toaster } from "./sonner.svelte";
@@ -0,0 +1,20 @@
<script lang="ts">
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
import { mode } from "mode-watcher";
let restProps: SonnerProps = $props();
</script>
<Sonner
theme={$mode}
class="toaster group"
toastOptions={{
classes: {
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...restProps}
/>
+3 -1
View File
@@ -1,5 +1,7 @@
import { toast } from 'svelte-sonner';
export const notify = (message: string): void => {
alert(message);
toast(message);
};
export const prompt = (message: string): boolean => {
+3
View File
@@ -86,6 +86,9 @@ export class PanZoomState {
public resize() {
this.pzoom?.resize();
if (!this.isDirty) {
this.reset();
}
}
public zoomIn() {
+4
View File
@@ -1,10 +1,12 @@
<script lang="ts">
import { base } from '$app/paths';
import { Toaster } from '$lib/components/ui/sonner/index.js';
import { loadingStateStore } from '$lib/util/loading';
import { toggleDarkTheme } from '$lib/util/state';
import { initHandler } from '$lib/util/util';
import { mode, ModeWatcher } from 'mode-watcher';
import { onMount, type Snippet } from 'svelte';
import '../app.postcss';
interface Props {
@@ -39,6 +41,8 @@
</script>
<ModeWatcher />
<Toaster />
<main class="text-primary-content h-screen">
{@render children?.()}
</main>
+47 -11
View File
@@ -1,9 +1,12 @@
<script lang="ts">
import History from '$/components/History/History.svelte';
import PanZoomToolbar from '$/components/PanZoomToolbar.svelte';
import SyncRoughToolbar from '$/components/SyncRoughToolbar.svelte';
import { Button } from '$/components/ui/button';
import * as Resizable from '$/components/ui/resizable';
import { Toggle } from '$/components/ui/toggle';
import VersionSecurityToolbar from '$/components/VersionSecurityToolbar.svelte';
import { env } from '$/util/env';
import { PanZoomState } from '$/util/panZoom';
import Actions from '$lib/components/Actions.svelte';
import Card from '$lib/components/Card/Card.svelte';
@@ -13,10 +16,11 @@
import View from '$lib/components/View.svelte';
import type { DocumentationConfig, EditorMode, Tab, ValidatedState } from '$lib/types';
import { stateStore, updateCodeStore } from '$lib/util/state';
import { initHandler } from '$lib/util/util';
import { initHandler, MCBaseURL } from '$lib/util/util';
import { onMount } from 'svelte';
import BookIcon from '~icons/material-symbols/book-2-outline-rounded';
import CodeIcon from '~icons/material-symbols/code-blocks-outline';
import HistoryIcon from '~icons/material-symbols/history';
import GearIcon from '~icons/material-symbols/settings-outline-rounded';
const panZoomState = new PanZoomState();
@@ -107,7 +111,7 @@
updateCodeStore({ editorMode });
};
const tabs: Tab[] = [
const editorTabs: Tab[] = [
{
id: 'code',
title: 'Code',
@@ -123,15 +127,36 @@
onMount(async () => {
await initHandler();
});
let isHistoryOpen = $state(false);
</script>
<div class="flex h-full flex-col overflow-hidden">
<Navbar />
<Navbar>
<Button size="sm">Share</Button>
<Toggle bind:pressed={isHistoryOpen}>
<HistoryIcon />
</Toggle>
{#if env.isEnabledMermaidChartLinks}
<Button
size="sm"
href={`${MCBaseURL}/app/plugin/save?state=${$stateStore.serialized}`}
target="_blank">
<img class="size-4" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />
Save diagram
</Button>
{/if}
</Navbar>
<div class="flex flex-1 overflow-hidden">
<Resizable.PaneGroup direction="horizontal" autoSaveId="liveEditor" class="p-6 pt-0">
<Resizable.Pane defaultSize={40} minSize={15} class="hidden md:block">
<div class="flex h-full flex-col gap-6" id="editorPane">
<Card onselect={tabSelectHandler} isOpen {tabs} {activeTabID} isClosable={false}>
<Card
onselect={tabSelectHandler}
isOpen
tabs={editorTabs}
{activeTabID}
isClosable={false}>
{#snippet actions()}
<div class="flex flex-row items-center">
<Button
@@ -203,13 +228,24 @@
</div>
{/snippet} -->
<!-- <div class="flex-1 overflow-auto"> -->
<View {panZoomState} />
<div class="absolute right-0 top-0"><PanZoomToolbar {panZoomState} /></div>
<div class="absolute bottom-0 left-5"><SyncRoughToolbar /></div>
<div class="absolute bottom-0 right-0"><VersionSecurityToolbar /></div>
<!-- </div> -->
<!-- </Card> -->
<Resizable.PaneGroup direction="horizontal" autoSaveId="viewAndHistory" class="">
<Resizable.Pane minSize={15} class="relative">
<!-- <div class="flex-1 overflow-auto"> -->
<View {panZoomState} />
<div class="absolute right-0 top-0"><PanZoomToolbar {panZoomState} /></div>
<div class="absolute bottom-0 right-0"><VersionSecurityToolbar /></div>
<div class="absolute bottom-0 left-5"><SyncRoughToolbar /></div>
</Resizable.Pane>
{#if isHistoryOpen}
<Resizable.Handle class="ml-2 opacity-0" />
<Resizable.Pane minSize={25} defaultSize={40}>
<div class="flex h-full flex-grow flex-col">
<History />
</div>
</Resizable.Pane>
{/if}
</Resizable.PaneGroup>
<div class="mx-2 rounded p-2 shadow md:hidden">
Code editing not supported on mobile. Please use a desktop browser.
</div>
+5
View File
@@ -6337,6 +6337,11 @@ svelte-preprocess@^6.0.0:
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz#fdc1f9dc41b6f22bf8b1f059e9f21eaaae181eeb"
integrity sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==
svelte-sonner@^0.3.28:
version "0.3.28"
resolved "https://registry.yarnpkg.com/svelte-sonner/-/svelte-sonner-0.3.28.tgz#b24b3e7af924d5c50ca2e33cede1382283074abf"
integrity sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==
svelte-toolbelt@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz#63e9c6629456a1a0dd57812f85426e4631fd754e"