From 4bdeaf999b81be289f121874eb9201b79b9e6f84 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:50:30 +0100 Subject: [PATCH] feat: TTD dialog UI tweaks (#7384) --- .../TTDDialog/MermaidToExcalidraw.tsx | 70 ++++++++----------- src/components/TTDDialog/TTDDialog.scss | 14 ++++ src/components/TTDDialog/TTDDialog.tsx | 51 +++++++++++++- src/components/TTDDialog/TTDDialogPanel.tsx | 5 ++ .../TTDDialog/TTDDialogSubmitShortcut.tsx | 14 ++++ src/components/TTDDialog/common.ts | 21 +++--- .../dropdownMenu/DropdownMenuItem.tsx | 6 +- src/constants.ts | 1 + src/css/styles.scss | 6 ++ .../MermaidToExcalidraw.test.tsx.snap | 2 +- 10 files changed, 134 insertions(+), 56 deletions(-) create mode 100644 src/components/TTDDialog/TTDDialogSubmitShortcut.tsx diff --git a/src/components/TTDDialog/MermaidToExcalidraw.tsx b/src/components/TTDDialog/MermaidToExcalidraw.tsx index 342bf38b..df9f4b8b 100644 --- a/src/components/TTDDialog/MermaidToExcalidraw.tsx +++ b/src/components/TTDDialog/MermaidToExcalidraw.tsx @@ -7,7 +7,6 @@ import "./MermaidToExcalidraw.scss"; import { t } from "../../i18n"; import Trans from "../Trans"; import { - LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, MermaidToExcalidrawLibProps, convertMermaidToExcalidraw, insertToEditor, @@ -17,30 +16,26 @@ import { TTDDialogPanels } from "./TTDDialogPanels"; import { TTDDialogPanel } from "./TTDDialogPanel"; import { TTDDialogInput } from "./TTDDialogInput"; import { TTDDialogOutput } from "./TTDDialogOutput"; +import { EditorLocalStorage } from "../../data/EditorLocalStorage"; +import { EDITOR_LS_KEYS } from "../../constants"; +import { debounce } from "../../utils"; +import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut"; const MERMAID_EXAMPLE = "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]"; -const importMermaidDataFromStorage = () => { - try { - const data = localStorage.getItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW); - if (data) { - return data; - } - } catch (error: any) { - // Unable to access localStorage - console.error(error); - } - - return null; -}; +const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300); const MermaidToExcalidraw = ({ mermaidToExcalidrawLib, }: { mermaidToExcalidrawLib: MermaidToExcalidrawLibProps; }) => { - const [text, setText] = useState(""); + const [text, setText] = useState( + () => + EditorLocalStorage.get(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) || + MERMAID_EXAMPLE, + ); const deferredText = useDeferredValue(text.trim()); const [error, setError] = useState(null); @@ -52,11 +47,6 @@ const MermaidToExcalidraw = ({ const app = useApp(); - useEffect(() => { - const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE; - setText(data); - }, []); - useEffect(() => { convertMermaidToExcalidraw({ canvasRef, @@ -65,22 +55,25 @@ const MermaidToExcalidraw = ({ setError, mermaidDefinition: deferredText, }).catch(() => {}); + + debouncedSaveMermaidDefinition(deferredText); }, [deferredText, mermaidToExcalidrawLib]); - const textRef = useRef(text); + useEffect( + () => () => { + debouncedSaveMermaidDefinition.flush(); + }, + [], + ); - // slightly hacky but really quite simple - // essentially, we want to save the text to LS when the component unmounts - useEffect(() => { - textRef.current = text; - }, [text]); - useEffect(() => { - return () => { - if (textRef.current) { - saveMermaidDataToStorage(textRef.current); - } - }; - }, []); + const onInsertToEditor = () => { + insertToEditor({ + app, + data, + text, + shouldSaveMermaidDataToStorage: true, + }); + }; return ( <> @@ -103,22 +96,21 @@ const MermaidToExcalidraw = ({ input={text} placeholder={"Write Mermaid diagram defintion here..."} onChange={(event) => setText(event.target.value)} + onKeyboardSubmit={() => { + onInsertToEditor(); + }} /> { - insertToEditor({ - app, - data, - text, - shouldSaveMermaidDataToStorage: true, - }); + onInsertToEditor(); }, label: t("mermaid.button"), icon: ArrowRightIcon, }} + renderSubmitShortcut={() => } > (null); +const ttdGenerationAtom = atom<{ + generatedResponse: string | null; + prompt: string | null; +} | null>(null); + type OnTestSubmitRetValue = { rateLimit?: number | null; rateLimitRemaining?: number | null; @@ -80,10 +87,13 @@ export const TTDDialogBase = withInternalFallback( | { __fallback: true } )) => { const app = useApp(); + const setAppState = useExcalidrawSetAppState(); const someRandomDivRef = useRef(null); - const [text, setText] = useState(""); + const [ttdGeneration, setTtdGeneration] = useAtom(ttdGenerationAtom); + + const [text, setText] = useState(ttdGeneration?.prompt ?? ""); const prompt = text.trim(); @@ -91,6 +101,10 @@ export const TTDDialogBase = withInternalFallback( event, ) => { setText(event.target.value); + setTtdGeneration((s) => ({ + generatedResponse: s?.generatedResponse ?? null, + prompt: event.target.value, + })); }; const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false); @@ -131,6 +145,13 @@ export const TTDDialogBase = withInternalFallback( const { generatedResponse, error, rateLimit, rateLimitRemaining } = await rest.onTextSubmit(prompt); + if (typeof generatedResponse === "string") { + setTtdGeneration((s) => ({ + generatedResponse, + prompt: s?.prompt ?? null, + })); + } + if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) { setRateLimits({ rateLimit, rateLimitRemaining }); } @@ -153,7 +174,6 @@ export const TTDDialogBase = withInternalFallback( mermaidDefinition: generatedResponse, }); trackEvent("ai", "mermaid parse success", "ttd"); - saveMermaidDataToStorage(generatedResponse); } catch (error: any) { console.info( `%cTTD mermaid render errror: ${error.message}`, @@ -293,7 +313,32 @@ export const TTDDialogBase = withInternalFallback( ); }} + renderSubmitShortcut={() => } renderBottomRight={() => { + if (typeof ttdGeneration?.generatedResponse === "string") { + return ( +
{ + if ( + typeof ttdGeneration?.generatedResponse === + "string" + ) { + saveMermaidDataToStorage( + ttdGeneration.generatedResponse, + ); + setAppState({ + openDialog: { name: "ttd", tab: "mermaid" }, + }); + } + }} + > + View as Mermaid + +
+ ); + } const ratio = prompt.length / MAX_PROMPT_LENGTH; if (ratio > 0.8) { return ( diff --git a/src/components/TTDDialog/TTDDialogPanel.tsx b/src/components/TTDDialog/TTDDialogPanel.tsx index b74b7489..5c7fba6d 100644 --- a/src/components/TTDDialog/TTDDialogPanel.tsx +++ b/src/components/TTDDialog/TTDDialogPanel.tsx @@ -14,6 +14,7 @@ interface TTDDialogPanelProps { panelActionDisabled?: boolean; onTextSubmitInProgess?: boolean; renderTopRight?: () => ReactNode; + renderSubmitShortcut?: () => ReactNode; renderBottomRight?: () => ReactNode; } @@ -24,6 +25,7 @@ export const TTDDialogPanel = ({ panelActionDisabled = false, onTextSubmitInProgess, renderTopRight, + renderSubmitShortcut, renderBottomRight, }: TTDDialogPanelProps) => { return ( @@ -51,6 +53,9 @@ export const TTDDialogPanel = ({ {onTextSubmitInProgess && } + {!panelActionDisabled && + !onTextSubmitInProgess && + renderSubmitShortcut?.()} {renderBottomRight?.()} diff --git a/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx b/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx new file mode 100644 index 00000000..a8831e3a --- /dev/null +++ b/src/components/TTDDialog/TTDDialogSubmitShortcut.tsx @@ -0,0 +1,14 @@ +import { getShortcutKey } from "../../utils"; + +export const TTDDialogSubmitShortcut = () => { + return ( +
+
+ {getShortcutKey("CtrlOrCmd")} +
+
+ {getShortcutKey("Enter")} +
+
+ ); +}; diff --git a/src/components/TTDDialog/common.ts b/src/components/TTDDialog/common.ts index 9d90a043..5973d911 100644 --- a/src/components/TTDDialog/common.ts +++ b/src/components/TTDDialog/common.ts @@ -1,6 +1,10 @@ import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw"; import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces"; -import { DEFAULT_EXPORT_PADDING, DEFAULT_FONT_SIZE } from "../../constants"; +import { + DEFAULT_EXPORT_PADDING, + DEFAULT_FONT_SIZE, + EDITOR_LS_KEYS, +} from "../../constants"; import { convertToExcalidrawElements, exportToCanvas, @@ -8,6 +12,7 @@ import { import { NonDeletedExcalidrawElement } from "../../element/types"; import { AppClassProperties, BinaryFiles } from "../../types"; import { canvasToBlob } from "../../data/blob"; +import { EditorLocalStorage } from "../../data/EditorLocalStorage"; const resetPreview = ({ canvasRef, @@ -110,7 +115,6 @@ export const convertMermaidToExcalidraw = async ({ parent.style.background = "var(--default-bg-color)"; canvasNode.replaceChildren(canvas); } catch (err: any) { - console.error(err); parent.style.background = "var(--default-bg-color)"; if (mermaidDefinition) { setError(err); @@ -120,14 +124,11 @@ export const convertMermaidToExcalidraw = async ({ } }; -export const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; -export const saveMermaidDataToStorage = (data: string) => { - try { - localStorage.setItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, data); - } catch (error: any) { - // Unable to access window.localStorage - console.error(error); - } +export const saveMermaidDataToStorage = (mermaidDefinition: string) => { + EditorLocalStorage.set( + EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW, + mermaidDefinition, + ); }; export const insertToEditor = ({ diff --git a/src/components/dropdownMenu/DropdownMenuItem.tsx b/src/components/dropdownMenu/DropdownMenuItem.tsx index 1d92e1f1..c3a165ea 100644 --- a/src/components/dropdownMenu/DropdownMenuItem.tsx +++ b/src/components/dropdownMenu/DropdownMenuItem.tsx @@ -49,12 +49,12 @@ export const DropDownMenuItemBadge = ({ style={{ display: "inline-flex", marginLeft: "auto", - padding: "1px 4px", + padding: "2px 4px", background: "pink", borderRadius: 6, - fontSize: 11, + fontSize: 9, color: "black", - fontFamily: "monospace", + fontFamily: "Cascadia, monospace", }} > {children} diff --git a/src/constants.ts b/src/constants.ts index 90270620..5594f356 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -373,5 +373,6 @@ export const TOOL_TYPE = { export const EDITOR_LS_KEYS = { OAI_API_KEY: "excalidraw-oai-api-key", // legacy naming (non)scheme + MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw", PUBLISH_LIBRARY: "publish-library-data", } as const; diff --git a/src/css/styles.scss b/src/css/styles.scss index d7202c6a..cafd21fb 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -53,14 +53,20 @@ // component (e.g. if you select text in a sidebar) user-select: none; + .excalidraw-link, a { font-weight: 500; text-decoration: none; color: var(--link-color); + user-select: none; + cursor: pointer; &:hover { text-decoration: underline; } + &:active { + text-decoration: none; + } } canvas { diff --git a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap index ce35ead1..c9338966 100644 --- a/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap +++ b/src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap @@ -6,5 +6,5 @@ exports[`Test > should open mermaid popup when active too B --> C{Let me think} C -->|One| D[Laptop] C -->|Two| E[iPhone] - C -->|Three| F[Car]
" + C -->|Three| F[Car]
Ctrl
Enter
" `;