From 4799f0f4633942edf0b7c76a643000b5ba5847f4 Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Sun, 27 Aug 2023 19:44:23 -0700 Subject: [PATCH] Add saved prompt deletion with confirmation --- TODO.md | 2 +- src/common/saving/localstorage.ts | 29 +++++++++++++++++++++++++---- src/components/SavedPrompt.tsx | 23 ++++++++++++++++++++++- src/main.tsx | 15 +++++++++------ 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 48b9ea9..9529f2b 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,7 @@ - [x] Save current prompt to local storage - [x] Save named prompts to local storage - [ ] Save history to local storage -- [ ] Allow deleting history items +- [x] Allow deleting history items ## Autocomplete diff --git a/src/common/saving/localstorage.ts b/src/common/saving/localstorage.ts index bb3d840..f8ccb30 100644 --- a/src/common/saving/localstorage.ts +++ b/src/common/saving/localstorage.ts @@ -1,6 +1,12 @@ import { RenderingOptions } from './../rendering/RenderType'; -import { BehaviorSubject, Observable, ReplaySubject, Subject } from 'rxjs'; -import { scan, takeUntil } from 'rxjs/operators'; +import { + BehaviorSubject, + Observable, + ReplaySubject, + Subject, + merge, +} from 'rxjs'; +import { scan, shareReplay, takeUntil } from 'rxjs/operators'; import { RenderingOptionsSchema, @@ -9,6 +15,7 @@ import { SavedPromptsSchema, } from './types'; import { getDefaultPrompts } from '../../examples/prompts'; +import { isArray, isEqual } from 'lodash'; /** Keys */ const ACTIVE_PROMPT_NAME = 'pc.active_prompt.name' as const; @@ -64,6 +71,13 @@ function savePromptsLocal(newPrompts: SavedPrompts) { localStorage.setItem(SAVED_PROMPTS, JSON.stringify(newPrompts)); } +export function deletePromptLocal(promptToDelete: SavedPrompt) { + const currentPrompts = getSavedPromptsLocal(); + const filtered = currentPrompts.filter((p) => !isEqual(p, promptToDelete)); + savePromptsLocal(filtered); + forceUpdatePromptsSubject.next(filtered); +} + export function getSavedPromptsLocal(): SavedPrompts { const currentValue = localStorage.getItem(SAVED_PROMPTS); const parsedMaybe = SavedPromptsSchema.safeParse( @@ -83,13 +97,19 @@ export function getSavedPromptsLocal(): SavedPrompts { const activePromptSubject = new BehaviorSubject(getActivePromptLocal()); const activePromptNameSubject = new BehaviorSubject(getActivePromptNameLocal()); const activePromptTagsSubject = new BehaviorSubject(getActivePromptTagsLocal()); +const forceUpdatePromptsSubject = new Subject(); const savedPromptInputSubject = new Subject(); -const savedPromptsComposed = savedPromptInputSubject.pipe( +const savedPromptsComposed = merge( + savedPromptInputSubject, + forceUpdatePromptsSubject, +).pipe( scan( - (existingPrompts, newPrompt) => [...existingPrompts, newPrompt], + (existingPrompts, newPrompt) => + isArray(newPrompt) ? newPrompt : [...existingPrompts, newPrompt], getSavedPromptsLocal(), ), + shareReplay(1), ); /* Exports */ @@ -111,6 +131,7 @@ export const savedPrompts$: Observable = savedPromptsComposed; export function savePrompt(prompt: SavedPrompt) { savedPromptInputSubject.next(prompt); } + /* Persistence */ activePrompt$ .pipe(takeUntil(cleanupLocalStorageSubscriptions$)) diff --git a/src/components/SavedPrompt.tsx b/src/components/SavedPrompt.tsx index 659c588..ec7ecca 100644 --- a/src/components/SavedPrompt.tsx +++ b/src/components/SavedPrompt.tsx @@ -1,7 +1,9 @@ import { Chip, Collapse, Tooltip, IconButton } from '@mui/material'; import { SavedPrompt } from '../common/saving/types'; import { useRef, useState } from 'react'; -import { NorthWest } from '@mui/icons-material'; +import { DeleteForever, NorthWest } from '@mui/icons-material'; +import { useConfirm } from 'material-ui-confirm'; +import { deletePromptLocal } from '../common/saving/localstorage'; export interface SavedPromptDisplayProps { prompt: SavedPrompt; @@ -15,6 +17,7 @@ export function SavedPromptDisplay({ const { name, contents, tags } = prompt; const [open, setOpen] = useState(false); + const confirm = useConfirm(); const promptText = useRef(null); @@ -27,6 +30,18 @@ export function SavedPromptDisplay({ window.getSelection()?.selectAllChildren(promptTextNode); } + async function handleDelete() { + try { + await confirm({ + description: `Are you sure you want to delete ${name}?`, + }); + + deletePromptLocal(prompt); + } catch (err: unknown) { + console.error(`Canceled deletion of ${name}\n${err}`); + } + } + return (

@@ -58,6 +73,12 @@ export function SavedPromptDisplay({ + + + + + +


diff --git a/src/main.tsx b/src/main.tsx index 4a461fd..49b57d2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,12 +1,13 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App.tsx'; -import './index.css'; import { Button, ThemeProvider, createTheme } from '@mui/material'; import { red } from '@mui/material/colors'; -import { BrowserRouter } from 'react-router-dom'; +import { ConfirmProvider } from 'material-ui-confirm'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App.tsx'; import { saveActivePrompt } from './common/saving/localstorage.ts'; +import './index.css'; const theme = createTheme({ palette: { @@ -51,7 +52,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - + + +