feat: TTD dialog UI tweaks (#7384)

This commit is contained in:
David Luzar 2023-12-04 17:50:30 +01:00 committed by GitHub
parent 42d8c5a040
commit 4bdeaf999b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 56 deletions

View File

@ -7,7 +7,6 @@ import "./MermaidToExcalidraw.scss";
import { t } from "../../i18n"; import { t } from "../../i18n";
import Trans from "../Trans"; import Trans from "../Trans";
import { import {
LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW,
MermaidToExcalidrawLibProps, MermaidToExcalidrawLibProps,
convertMermaidToExcalidraw, convertMermaidToExcalidraw,
insertToEditor, insertToEditor,
@ -17,30 +16,26 @@ import { TTDDialogPanels } from "./TTDDialogPanels";
import { TTDDialogPanel } from "./TTDDialogPanel"; import { TTDDialogPanel } from "./TTDDialogPanel";
import { TTDDialogInput } from "./TTDDialogInput"; import { TTDDialogInput } from "./TTDDialogInput";
import { TTDDialogOutput } from "./TTDDialogOutput"; 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 = 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]"; "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 = () => { const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300);
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 MermaidToExcalidraw = ({ const MermaidToExcalidraw = ({
mermaidToExcalidrawLib, mermaidToExcalidrawLib,
}: { }: {
mermaidToExcalidrawLib: MermaidToExcalidrawLibProps; mermaidToExcalidrawLib: MermaidToExcalidrawLibProps;
}) => { }) => {
const [text, setText] = useState(""); const [text, setText] = useState(
() =>
EditorLocalStorage.get<string>(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) ||
MERMAID_EXAMPLE,
);
const deferredText = useDeferredValue(text.trim()); const deferredText = useDeferredValue(text.trim());
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
@ -52,11 +47,6 @@ const MermaidToExcalidraw = ({
const app = useApp(); const app = useApp();
useEffect(() => {
const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE;
setText(data);
}, []);
useEffect(() => { useEffect(() => {
convertMermaidToExcalidraw({ convertMermaidToExcalidraw({
canvasRef, canvasRef,
@ -65,22 +55,25 @@ const MermaidToExcalidraw = ({
setError, setError,
mermaidDefinition: deferredText, mermaidDefinition: deferredText,
}).catch(() => {}); }).catch(() => {});
debouncedSaveMermaidDefinition(deferredText);
}, [deferredText, mermaidToExcalidrawLib]); }, [deferredText, mermaidToExcalidrawLib]);
const textRef = useRef(text); useEffect(
() => () => {
debouncedSaveMermaidDefinition.flush();
},
[],
);
// slightly hacky but really quite simple const onInsertToEditor = () => {
// essentially, we want to save the text to LS when the component unmounts insertToEditor({
useEffect(() => { app,
textRef.current = text; data,
}, [text]); text,
useEffect(() => { shouldSaveMermaidDataToStorage: true,
return () => { });
if (textRef.current) { };
saveMermaidDataToStorage(textRef.current);
}
};
}, []);
return ( return (
<> <>
@ -103,22 +96,21 @@ const MermaidToExcalidraw = ({
input={text} input={text}
placeholder={"Write Mermaid diagram defintion here..."} placeholder={"Write Mermaid diagram defintion here..."}
onChange={(event) => setText(event.target.value)} onChange={(event) => setText(event.target.value)}
onKeyboardSubmit={() => {
onInsertToEditor();
}}
/> />
</TTDDialogPanel> </TTDDialogPanel>
<TTDDialogPanel <TTDDialogPanel
label={t("mermaid.preview")} label={t("mermaid.preview")}
panelAction={{ panelAction={{
action: () => { action: () => {
insertToEditor({ onInsertToEditor();
app,
data,
text,
shouldSaveMermaidDataToStorage: true,
});
}, },
label: t("mermaid.button"), label: t("mermaid.button"),
icon: ArrowRightIcon, icon: ArrowRightIcon,
}} }}
renderSubmitShortcut={() => <TTDDialogSubmitShortcut />}
> >
<TTDDialogOutput <TTDDialogOutput
canvasRef={canvasRef} canvasRef={canvasRef}

View File

@ -298,4 +298,18 @@ $verticalBreakpoint: 861px;
} }
} }
} }
.ttd-dialog-submit-shortcut {
margin-inline-start: 0.5rem;
font-size: 0.625rem;
opacity: 0.6;
display: flex;
gap: 0.125rem;
&__key {
border: 1px solid gray;
padding: 2px 3px;
border-radius: 4px;
}
}
} }

View File

@ -1,5 +1,5 @@
import { Dialog } from "../Dialog"; import { Dialog } from "../Dialog";
import { useApp } from "../App"; import { useApp, useExcalidrawSetAppState } from "../App";
import MermaidToExcalidraw from "./MermaidToExcalidraw"; import MermaidToExcalidraw from "./MermaidToExcalidraw";
import TTDDialogTabs from "./TTDDialogTabs"; import TTDDialogTabs from "./TTDDialogTabs";
import { ChangeEventHandler, useEffect, useRef, useState } from "react"; import { ChangeEventHandler, useEffect, useRef, useState } from "react";
@ -27,6 +27,8 @@ import "./TTDDialog.scss";
import { isFiniteNumber } from "../../utils"; import { isFiniteNumber } from "../../utils";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../analytics";
import { InlineIcon } from "../InlineIcon";
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
const MIN_PROMPT_LENGTH = 3; const MIN_PROMPT_LENGTH = 3;
const MAX_PROMPT_LENGTH = 1000; const MAX_PROMPT_LENGTH = 1000;
@ -36,6 +38,11 @@ const rateLimitsAtom = atom<{
rateLimitRemaining: number; rateLimitRemaining: number;
} | null>(null); } | null>(null);
const ttdGenerationAtom = atom<{
generatedResponse: string | null;
prompt: string | null;
} | null>(null);
type OnTestSubmitRetValue = { type OnTestSubmitRetValue = {
rateLimit?: number | null; rateLimit?: number | null;
rateLimitRemaining?: number | null; rateLimitRemaining?: number | null;
@ -80,10 +87,13 @@ export const TTDDialogBase = withInternalFallback(
| { __fallback: true } | { __fallback: true }
)) => { )) => {
const app = useApp(); const app = useApp();
const setAppState = useExcalidrawSetAppState();
const someRandomDivRef = useRef<HTMLDivElement>(null); const someRandomDivRef = useRef<HTMLDivElement>(null);
const [text, setText] = useState(""); const [ttdGeneration, setTtdGeneration] = useAtom(ttdGenerationAtom);
const [text, setText] = useState(ttdGeneration?.prompt ?? "");
const prompt = text.trim(); const prompt = text.trim();
@ -91,6 +101,10 @@ export const TTDDialogBase = withInternalFallback(
event, event,
) => { ) => {
setText(event.target.value); setText(event.target.value);
setTtdGeneration((s) => ({
generatedResponse: s?.generatedResponse ?? null,
prompt: event.target.value,
}));
}; };
const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false); const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false);
@ -131,6 +145,13 @@ export const TTDDialogBase = withInternalFallback(
const { generatedResponse, error, rateLimit, rateLimitRemaining } = const { generatedResponse, error, rateLimit, rateLimitRemaining } =
await rest.onTextSubmit(prompt); await rest.onTextSubmit(prompt);
if (typeof generatedResponse === "string") {
setTtdGeneration((s) => ({
generatedResponse,
prompt: s?.prompt ?? null,
}));
}
if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) { if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) {
setRateLimits({ rateLimit, rateLimitRemaining }); setRateLimits({ rateLimit, rateLimitRemaining });
} }
@ -153,7 +174,6 @@ export const TTDDialogBase = withInternalFallback(
mermaidDefinition: generatedResponse, mermaidDefinition: generatedResponse,
}); });
trackEvent("ai", "mermaid parse success", "ttd"); trackEvent("ai", "mermaid parse success", "ttd");
saveMermaidDataToStorage(generatedResponse);
} catch (error: any) { } catch (error: any) {
console.info( console.info(
`%cTTD mermaid render errror: ${error.message}`, `%cTTD mermaid render errror: ${error.message}`,
@ -293,7 +313,32 @@ export const TTDDialogBase = withInternalFallback(
</div> </div>
); );
}} }}
renderSubmitShortcut={() => <TTDDialogSubmitShortcut />}
renderBottomRight={() => { renderBottomRight={() => {
if (typeof ttdGeneration?.generatedResponse === "string") {
return (
<div
className="excalidraw-link"
style={{ marginLeft: "auto", fontSize: 14 }}
onClick={() => {
if (
typeof ttdGeneration?.generatedResponse ===
"string"
) {
saveMermaidDataToStorage(
ttdGeneration.generatedResponse,
);
setAppState({
openDialog: { name: "ttd", tab: "mermaid" },
});
}
}}
>
View as Mermaid
<InlineIcon icon={ArrowRightIcon} />
</div>
);
}
const ratio = prompt.length / MAX_PROMPT_LENGTH; const ratio = prompt.length / MAX_PROMPT_LENGTH;
if (ratio > 0.8) { if (ratio > 0.8) {
return ( return (

View File

@ -14,6 +14,7 @@ interface TTDDialogPanelProps {
panelActionDisabled?: boolean; panelActionDisabled?: boolean;
onTextSubmitInProgess?: boolean; onTextSubmitInProgess?: boolean;
renderTopRight?: () => ReactNode; renderTopRight?: () => ReactNode;
renderSubmitShortcut?: () => ReactNode;
renderBottomRight?: () => ReactNode; renderBottomRight?: () => ReactNode;
} }
@ -24,6 +25,7 @@ export const TTDDialogPanel = ({
panelActionDisabled = false, panelActionDisabled = false,
onTextSubmitInProgess, onTextSubmitInProgess,
renderTopRight, renderTopRight,
renderSubmitShortcut,
renderBottomRight, renderBottomRight,
}: TTDDialogPanelProps) => { }: TTDDialogPanelProps) => {
return ( return (
@ -51,6 +53,9 @@ export const TTDDialogPanel = ({
</div> </div>
{onTextSubmitInProgess && <Spinner />} {onTextSubmitInProgess && <Spinner />}
</Button> </Button>
{!panelActionDisabled &&
!onTextSubmitInProgess &&
renderSubmitShortcut?.()}
{renderBottomRight?.()} {renderBottomRight?.()}
</div> </div>
</div> </div>

View File

@ -0,0 +1,14 @@
import { getShortcutKey } from "../../utils";
export const TTDDialogSubmitShortcut = () => {
return (
<div className="ttd-dialog-submit-shortcut">
<div className="ttd-dialog-submit-shortcut__key">
{getShortcutKey("CtrlOrCmd")}
</div>
<div className="ttd-dialog-submit-shortcut__key">
{getShortcutKey("Enter")}
</div>
</div>
);
};

View File

@ -1,6 +1,10 @@
import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw"; import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw";
import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces"; 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 { import {
convertToExcalidrawElements, convertToExcalidrawElements,
exportToCanvas, exportToCanvas,
@ -8,6 +12,7 @@ import {
import { NonDeletedExcalidrawElement } from "../../element/types"; import { NonDeletedExcalidrawElement } from "../../element/types";
import { AppClassProperties, BinaryFiles } from "../../types"; import { AppClassProperties, BinaryFiles } from "../../types";
import { canvasToBlob } from "../../data/blob"; import { canvasToBlob } from "../../data/blob";
import { EditorLocalStorage } from "../../data/EditorLocalStorage";
const resetPreview = ({ const resetPreview = ({
canvasRef, canvasRef,
@ -110,7 +115,6 @@ export const convertMermaidToExcalidraw = async ({
parent.style.background = "var(--default-bg-color)"; parent.style.background = "var(--default-bg-color)";
canvasNode.replaceChildren(canvas); canvasNode.replaceChildren(canvas);
} catch (err: any) { } catch (err: any) {
console.error(err);
parent.style.background = "var(--default-bg-color)"; parent.style.background = "var(--default-bg-color)";
if (mermaidDefinition) { if (mermaidDefinition) {
setError(err); setError(err);
@ -120,14 +124,11 @@ export const convertMermaidToExcalidraw = async ({
} }
}; };
export const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; export const saveMermaidDataToStorage = (mermaidDefinition: string) => {
export const saveMermaidDataToStorage = (data: string) => { EditorLocalStorage.set(
try { EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW,
localStorage.setItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, data); mermaidDefinition,
} catch (error: any) { );
// Unable to access window.localStorage
console.error(error);
}
}; };
export const insertToEditor = ({ export const insertToEditor = ({

View File

@ -49,12 +49,12 @@ export const DropDownMenuItemBadge = ({
style={{ style={{
display: "inline-flex", display: "inline-flex",
marginLeft: "auto", marginLeft: "auto",
padding: "1px 4px", padding: "2px 4px",
background: "pink", background: "pink",
borderRadius: 6, borderRadius: 6,
fontSize: 11, fontSize: 9,
color: "black", color: "black",
fontFamily: "monospace", fontFamily: "Cascadia, monospace",
}} }}
> >
{children} {children}

View File

@ -373,5 +373,6 @@ export const TOOL_TYPE = {
export const EDITOR_LS_KEYS = { export const EDITOR_LS_KEYS = {
OAI_API_KEY: "excalidraw-oai-api-key", OAI_API_KEY: "excalidraw-oai-api-key",
// legacy naming (non)scheme // legacy naming (non)scheme
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
PUBLISH_LIBRARY: "publish-library-data", PUBLISH_LIBRARY: "publish-library-data",
} as const; } as const;

View File

@ -53,14 +53,20 @@
// component (e.g. if you select text in a sidebar) // component (e.g. if you select text in a sidebar)
user-select: none; user-select: none;
.excalidraw-link,
a { a {
font-weight: 500; font-weight: 500;
text-decoration: none; text-decoration: none;
color: var(--link-color); color: var(--link-color);
user-select: none;
cursor: pointer;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
&:active {
text-decoration: none;
}
} }
canvas { canvas {

View File

@ -6,5 +6,5 @@ exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active too
B --&gt; C{Let me think} B --&gt; C{Let me think}
C --&gt;|One| D[Laptop] C --&gt;|One| D[Laptop]
C --&gt;|Two| E[iPhone] C --&gt;|Two| E[iPhone]
C --&gt;|Three| F[Car]</textarea><div class=\\"ttd-dialog-panel-button-container invisible\\" style=\\"display: flex; align-items: center;\\"><button type=\\"button\\" class=\\"excalidraw-button ttd-dialog-panel-button\\"><div class=\\"\\"></div></button></div></div><div class=\\"ttd-dialog-panel\\"><div class=\\"ttd-dialog-panel__header\\"><label>Preview</label></div><div class=\\"ttd-dialog-output-wrapper\\"><div style=\\"opacity: 1;\\" class=\\"ttd-dialog-output-canvas-container\\"><canvas width=\\"89\\" height=\\"158\\" dir=\\"ltr\\"></canvas></div></div><div class=\\"ttd-dialog-panel-button-container\\" style=\\"display: flex; align-items: center;\\"><button type=\\"button\\" class=\\"excalidraw-button ttd-dialog-panel-button\\"><div class=\\"\\">Insert<span><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><g stroke-width=\\"1.25\\"><path d=\\"M4.16602 10H15.8327\\"></path><path d=\\"M12.5 13.3333L15.8333 10\\"></path><path d=\\"M12.5 6.66666L15.8333 9.99999\\"></path></g></svg></span></div></button></div></div></div></div></div></div></div></div></div>" C --&gt;|Three| F[Car]</textarea><div class=\\"ttd-dialog-panel-button-container invisible\\" style=\\"display: flex; align-items: center;\\"><button type=\\"button\\" class=\\"excalidraw-button ttd-dialog-panel-button\\"><div class=\\"\\"></div></button></div></div><div class=\\"ttd-dialog-panel\\"><div class=\\"ttd-dialog-panel__header\\"><label>Preview</label></div><div class=\\"ttd-dialog-output-wrapper\\"><div style=\\"opacity: 1;\\" class=\\"ttd-dialog-output-canvas-container\\"><canvas width=\\"89\\" height=\\"158\\" dir=\\"ltr\\"></canvas></div></div><div class=\\"ttd-dialog-panel-button-container\\" style=\\"display: flex; align-items: center;\\"><button type=\\"button\\" class=\\"excalidraw-button ttd-dialog-panel-button\\"><div class=\\"\\">Insert<span><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><g stroke-width=\\"1.25\\"><path d=\\"M4.16602 10H15.8327\\"></path><path d=\\"M12.5 13.3333L15.8333 10\\"></path><path d=\\"M12.5 6.66666L15.8333 9.99999\\"></path></g></svg></span></div></button><div class=\\"ttd-dialog-submit-shortcut\\"><div class=\\"ttd-dialog-submit-shortcut__key\\">Ctrl</div><div class=\\"ttd-dialog-submit-shortcut__key\\">Enter</div></div></div></div></div></div></div></div></div></div></div>"
`; `;