Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.51.0 #688

Merged
merged 3 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "1.50.0",
"version": "1.51.0",
"main": "module/module.js",
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/AiToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function AiToolbar() {
useEffect(() => {
if (userPasted) {
const timeout = setTimeout(() => {
useEditorStore.setState({ userPasted: false });
useEditorStore.setState({ userPasted: "" });
}, 15000);
return () => clearTimeout(timeout);
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/ConvertToFlowchart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function ConvertToFlowchart() {
})
.finally(() => {
stopConvert();
useEditorStore.setState({ userPasted: false });
useEditorStore.setState({ userPasted: "" });
});
}}
disabled={convertIsRunning}
Expand Down
38 changes: 36 additions & 2 deletions app/src/components/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { updateModelMarkers, useEditorStore } from "../lib/useEditorStore";
import Loading from "./Loading";
import { usePromptStore } from "../lib/usePromptStore";
import classNames from "classnames";
import { sanitizeOnPaste } from "../lib/sanitizeOnPaste";

type TextEditorProps = EditorProps & {
extendOptions?: editor.IEditorOptions;
Expand Down Expand Up @@ -67,8 +68,19 @@ export function TextEditor({ extendOptions = {}, ...props }: TextEditorProps) {
});

// Listen to when the user pastes into the document
editor.onDidPaste(() => {
useEditorStore.setState({ userPasted: true });
editor.onDidPaste((e) => {
// get the text in the range
const text = editor.getModel()?.getValueInRange(e.range);
if (text) {
// store it in the editor
useEditorStore.setState({ userPasted: text });

// sanitize it if necessary
const sanitized = sanitizeOnPaste(text);
if (sanitized) {
replaceRange(editor, e.range, sanitized);
}
}
});
}}
wrapperProps={{
Expand Down Expand Up @@ -113,3 +125,25 @@ function useEditorHover(hoverLineNumber?: number) {
};
}, [hoverLineNumber]);
}

/**
* Given the instance of the editor, the range to replace, and the new text
* replace that range with the new text
*/
/**
* Given the instance of the editor, the range to replace, and the new text
* replace that range with the new text
*/
function replaceRange(
editor: editor.IStandaloneCodeEditor,
range: editor.ISingleEditOperation["range"],
text: string
) {
editor.executeEdits("", [
{
range: range,
text: text,
forceMoveMarkers: true,
},
]);
}
36 changes: 36 additions & 0 deletions app/src/lib/sanitizeOnPaste.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { sanitizeOnPaste } from "./sanitizeOnPaste";

describe("sanitizeOnPaste", () => {
test("returns null for valid text", () => {
expect(sanitizeOnPaste("hello world")).toBe(null);
});
test("returns text with escaped parentheses for invalid text", () => {
expect(sanitizeOnPaste("hello (world)")).toBe("hello \\(world\\)");
});

test("returns escaped for multiple pointers", () => {
expect(sanitizeOnPaste("hello (world) (again)")).toBe(
"hello \\(world\\) \\(again\\)"
);
});

test("handles colon", () => {
expect(sanitizeOnPaste("hello: world")).toBe("hello\\: world");
});

test("Edge missing indentation", () => {
expect(
sanitizeOnPaste(
"export function TextEditor({ extendOptions = {}, ...props }: TextEditorProps) {"
)
).toBe(
"export function TextEditor({ extendOptions = {}, ...props }\\: TextEditorProps) {"
);
});

test("Pointer bug", () => {
expect(
sanitizeOnPaste(`export function sanitizeOnPaste(text: string) {`)
).toBe(`export function sanitizeOnPaste\\(text\\: string\\) {`);
});
});
40 changes: 40 additions & 0 deletions app/src/lib/sanitizeOnPaste.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { parse } from "graph-selector";
import { isError } from "./helpers";

/**
* Given the pasted text, this function checks if it is valid
* Flowchart Fun Syntax.
* - If it returns any errors which indicate
* parentheses issues, it will escape parentheses in the text
* and return the sanitized text.
*/
export function sanitizeOnPaste(text: string) {
let newText: string | null = null,
hasError = true,
count = 0;

while (hasError && count < 10) {
count++;
try {
parse(getText());
hasError = false;
} catch (error) {
if (isError(error)) {
if (error.message.includes("pointer")) {
newText = getText().replace(/[()]/g, "\\$&");
} else if (error.message.includes("label without parent")) {
newText = getText().replace(/:/g, "\\:");
} else if (error.message.includes("missing indentation")) {
newText = getText().replace(/:/g, "\\:");
} else {
console.log(error.message);
}
}
}
}
return newText;

function getText() {
return newText ? newText : text;
}
}
6 changes: 3 additions & 3 deletions app/src/lib/useEditorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ export const useEditorStore = create<{
markers: editor.IMarkerData[];
/** The current text selection */
selection: string;
/** Becomes true after the user pastes into the document */
userPasted: boolean;
/** Stores the text the user recently pasted into the editor */
userPasted: string;
}>((_set) => ({
editor: null,
monaco: null,
isDragging: false,
markers: [],
selection: "",
userPasted: false,
userPasted: "",
}));

export function updateModelMarkers() {
Expand Down
46 changes: 43 additions & 3 deletions app/src/pages/New.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { templates } from "../lib/templates/templates";
import { Trans, t } from "@lingui/macro";
import * as RadioGroup from "@radix-ui/react-radio-group";
import * as Tabs from "@radix-ui/react-tabs";
import { useCallback, useContext, useState } from "react";
import { Fragment, useCallback, useContext, useState } from "react";
import { AppContext } from "../components/AppContextProvider";
import { languages } from "../locales/i18n";
import { getFunFlowchartName } from "../lib/getFunFlowchartName";
Expand Down Expand Up @@ -144,6 +144,7 @@ export default function New2() {
);

const language = useContext(AppContext).language;
const [examples] = useState(createExamples());

return (
<form
Expand Down Expand Up @@ -222,14 +223,42 @@ export default function New2() {
</Trigger>
</div>
</Tabs.List>
<Tabs.Content value="ai" className="pt-4 grid gap-2">
<p className="text-neutral-700 leading-6 text-xs dark:text-neutral-300">
<Tabs.Content value="ai" className="pt-4 grid gap-1">
<p className="text-neutral-700 leading-6 text-sm dark:text-neutral-300">
<Trans>
Enter a prompt or information you would like to create a chart
from.
</Trans>
</p>
<div className="text-neutral-700 text-xs dark:text-neutral-300 leading-6">
<span className="font-bold">
<Trans>Examples:</Trans>&nbsp;&nbsp;
</span>
{examples.map((example, index) => (
<Fragment key={example}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<span
className="italic opacity-80 hover:opacity-100 cursor-pointer hover:underline"
role="button"
tabIndex={0}
onClick={() => {
// set example in textarea
const textarea = document.querySelector(
"#ai-prompt"
) as HTMLTextAreaElement;
if (textarea) textarea.value = example;
}}
>
{example}
</span>
{index < examples.length - 1 && (
<span className="mx-2">|</span>
)}
</Fragment>
))}
</div>
<Textarea
id="ai-prompt"
className="h-[120px]"
name="subject"
disabled={createChartMutation.isLoading}
Expand Down Expand Up @@ -309,3 +338,14 @@ function Section({
</section>
);
}

/**
* Prompt examples
*/
function createExamples() {
return [
t`Design a software development lifecycle flowchart for an agile team`,
t`Develop a decision tree for a CEO to evaluate potential new market opportunities`,
t`Create a flowchart showing the steps of planning and executing a school fundraising event`,
];
}
Loading