Skip to content

Commit

Permalink
feat: TTD dialog UI tweaks (excalidraw#7384)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelle authored Dec 4, 2023
1 parent 42d8c5a commit 4bdeaf9
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 56 deletions.
70 changes: 31 additions & 39 deletions src/components/TTDDialog/MermaidToExcalidraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<string>(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) ||
MERMAID_EXAMPLE,
);
const deferredText = useDeferredValue(text.trim());
const [error, setError] = useState<Error | null>(null);

Expand All @@ -52,11 +47,6 @@ const MermaidToExcalidraw = ({

const app = useApp();

useEffect(() => {
const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE;
setText(data);
}, []);

useEffect(() => {
convertMermaidToExcalidraw({
canvasRef,
Expand All @@ -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 (
<>
Expand All @@ -103,22 +96,21 @@ const MermaidToExcalidraw = ({
input={text}
placeholder={"Write Mermaid diagram defintion here..."}
onChange={(event) => setText(event.target.value)}
onKeyboardSubmit={() => {
onInsertToEditor();
}}
/>
</TTDDialogPanel>
<TTDDialogPanel
label={t("mermaid.preview")}
panelAction={{
action: () => {
insertToEditor({
app,
data,
text,
shouldSaveMermaidDataToStorage: true,
});
onInsertToEditor();
},
label: t("mermaid.button"),
icon: ArrowRightIcon,
}}
renderSubmitShortcut={() => <TTDDialogSubmitShortcut />}
>
<TTDDialogOutput
canvasRef={canvasRef}
Expand Down
14 changes: 14 additions & 0 deletions src/components/TTDDialog/TTDDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
51 changes: 48 additions & 3 deletions src/components/TTDDialog/TTDDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dialog } from "../Dialog";
import { useApp } from "../App";
import { useApp, useExcalidrawSetAppState } from "../App";
import MermaidToExcalidraw from "./MermaidToExcalidraw";
import TTDDialogTabs from "./TTDDialogTabs";
import { ChangeEventHandler, useEffect, useRef, useState } from "react";
Expand Down Expand Up @@ -27,6 +27,8 @@ import "./TTDDialog.scss";
import { isFiniteNumber } from "../../utils";
import { atom, useAtom } from "jotai";
import { trackEvent } from "../../analytics";
import { InlineIcon } from "../InlineIcon";
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";

const MIN_PROMPT_LENGTH = 3;
const MAX_PROMPT_LENGTH = 1000;
Expand All @@ -36,6 +38,11 @@ const rateLimitsAtom = atom<{
rateLimitRemaining: number;
} | null>(null);

const ttdGenerationAtom = atom<{
generatedResponse: string | null;
prompt: string | null;
} | null>(null);

type OnTestSubmitRetValue = {
rateLimit?: number | null;
rateLimitRemaining?: number | null;
Expand Down Expand Up @@ -80,17 +87,24 @@ export const TTDDialogBase = withInternalFallback(
| { __fallback: true }
)) => {
const app = useApp();
const setAppState = useExcalidrawSetAppState();

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 handleTextChange: ChangeEventHandler<HTMLTextAreaElement> = (
event,
) => {
setText(event.target.value);
setTtdGeneration((s) => ({
generatedResponse: s?.generatedResponse ?? null,
prompt: event.target.value,
}));
};

const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false);
Expand Down Expand Up @@ -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 });
}
Expand All @@ -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}`,
Expand Down Expand Up @@ -293,7 +313,32 @@ export const TTDDialogBase = withInternalFallback(
</div>
);
}}
renderSubmitShortcut={() => <TTDDialogSubmitShortcut />}
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;
if (ratio > 0.8) {
return (
Expand Down
5 changes: 5 additions & 0 deletions src/components/TTDDialog/TTDDialogPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface TTDDialogPanelProps {
panelActionDisabled?: boolean;
onTextSubmitInProgess?: boolean;
renderTopRight?: () => ReactNode;
renderSubmitShortcut?: () => ReactNode;
renderBottomRight?: () => ReactNode;
}

Expand All @@ -24,6 +25,7 @@ export const TTDDialogPanel = ({
panelActionDisabled = false,
onTextSubmitInProgess,
renderTopRight,
renderSubmitShortcut,
renderBottomRight,
}: TTDDialogPanelProps) => {
return (
Expand Down Expand Up @@ -51,6 +53,9 @@ export const TTDDialogPanel = ({
</div>
{onTextSubmitInProgess && <Spinner />}
</Button>
{!panelActionDisabled &&
!onTextSubmitInProgess &&
renderSubmitShortcut?.()}
{renderBottomRight?.()}
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions src/components/TTDDialog/TTDDialogSubmitShortcut.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
21 changes: 11 additions & 10 deletions src/components/TTDDialog/common.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
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,
} from "../../packages/excalidraw/index";
import { NonDeletedExcalidrawElement } from "../../element/types";
import { AppClassProperties, BinaryFiles } from "../../types";
import { canvasToBlob } from "../../data/blob";
import { EditorLocalStorage } from "../../data/EditorLocalStorage";

const resetPreview = ({
canvasRef,
Expand Down Expand Up @@ -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);
Expand All @@ -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 = ({
Expand Down
6 changes: 3 additions & 3 deletions src/components/dropdownMenu/DropdownMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
6 changes: 6 additions & 0 deletions src/css/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active too
B --&gt; C{Let me think}
C --&gt;|One| D[Laptop]
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>"
`;

0 comments on commit 4bdeaf9

Please sign in to comment.