feat: TTD dialog UI tweaks (#7384)
This commit is contained in:
parent
42d8c5a040
commit
4bdeaf999b
@ -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<string>(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) ||
|
||||
MERMAID_EXAMPLE,
|
||||
);
|
||||
const deferredText = useDeferredValue(text.trim());
|
||||
const [error, setError] = useState<Error | null>(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();
|
||||
}}
|
||||
/>
|
||||
</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}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -80,10 +87,13 @@ 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();
|
||||
|
||||
@ -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(
|
||||
</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 (
|
||||
|
@ -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 = ({
|
||||
</div>
|
||||
{onTextSubmitInProgess && <Spinner />}
|
||||
</Button>
|
||||
{!panelActionDisabled &&
|
||||
!onTextSubmitInProgess &&
|
||||
renderSubmitShortcut?.()}
|
||||
{renderBottomRight?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
14
src/components/TTDDialog/TTDDialogSubmitShortcut.tsx
Normal file
14
src/components/TTDDialog/TTDDialogSubmitShortcut.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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 = ({
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -6,5 +6,5 @@ exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active too
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|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 -->|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>"
|
||||
`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user