enhancement: prevent esc on codeAreaModal (#3232)

* add onEscapeKeyDown

* refactor: Add ConfirmationModal to CodeAreaModal for better user experience

* refactor: Update CodeAreaModal to use ReactAce for code editing

* fix closing o x modal

* refactor: Update CodeAreaModal to use ReactAce for code editing

* [autofix.ci] apply automated fixes

* decompose object

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
anovazzi1 2024-08-13 09:35:02 -03:00 • committed by GitHub
commit 665842680e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 13 deletions

View file

@ -148,6 +148,7 @@ interface BaseModalProps {
onChangeOpenModal?: (open?: boolean) => void; onChangeOpenModal?: (open?: boolean) => void;
type?: "modal" | "dialog"; type?: "modal" | "dialog";
onSubmit?: () => void; onSubmit?: () => void;
onEscapeKeyDown?: (e: KeyboardEvent) => void;
} }
function BaseModal({ function BaseModal({
open, open,
@ -157,6 +158,7 @@ function BaseModal({
onChangeOpenModal, onChangeOpenModal,
type = "dialog", type = "dialog",
onSubmit, onSubmit,
onEscapeKeyDown,
}: BaseModalProps) { }: BaseModalProps) {
const headerChild = React.Children.toArray(children).find( const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header, (child) => (child as React.ReactElement).type === Header,
@ -204,7 +206,10 @@ function BaseModal({
) : ( ) : (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
{triggerChild} {triggerChild}
<DialogContent className={contentClasses}> <DialogContent
onEscapeKeyDown={onEscapeKeyDown}
className={contentClasses}
>
{onSubmit ? ( {onSubmit ? (
<Form.Root <Form.Root
onSubmit={(event) => { onSubmit={(event) => {

View file

@ -4,8 +4,9 @@ import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight"; import "ace-builds/src-noconflict/theme-twilight";
// import "ace-builds/webpack-resolver"; // import "ace-builds/webpack-resolver";
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import AceEditor from "react-ace"; import AceEditor from "react-ace";
import ReactAce from "react-ace/lib/ace";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -26,6 +27,7 @@ import { useDarkStore } from "../../stores/darkStore";
import { CodeErrorDataTypeAPI } from "../../types/api"; import { CodeErrorDataTypeAPI } from "../../types/api";
import { codeAreaModalPropsType } from "../../types/components"; import { codeAreaModalPropsType } from "../../types/components";
import BaseModal from "../baseModal"; import BaseModal from "../baseModal";
import ConfirmationModal from "../confirmationModal";
export default function CodeAreaModal({ export default function CodeAreaModal({
value, value,
@ -47,6 +49,8 @@ export default function CodeAreaModal({
const [height, setHeight] = useState<string | null>(null); const [height, setHeight] = useState<string | null>(null);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [openConfirmation, setOpenConfirmation] = useState(false);
const codeRef = useRef<ReactAce | null>(null);
const [error, setError] = useState<{ const [error, setError] = useState<{
detail: CodeErrorDataTypeAPI; detail: CodeErrorDataTypeAPI;
} | null>(null); } | null>(null);
@ -123,10 +127,6 @@ export default function CodeAreaModal({
} }
} }
function handleClick() {
processCode();
}
useEffect(() => { useEffect(() => {
// Function to be executed after the state changes // Function to be executed after the state changes
const delayedFunction = setTimeout(() => { const delayedFunction = setTimeout(() => {
@ -143,12 +143,36 @@ export default function CodeAreaModal({
}; };
}, [error, setHeight]); }, [error, setHeight]);
useEffect(() => {
if (!openConfirmation) {
codeRef.current?.editor.focus();
}
}, [openConfirmation]);
useEffect(() => { useEffect(() => {
setCode(value); setCode(value);
}, [value, open]); }, [value, open]);
return ( return (
<BaseModal open={open} setOpen={setOpen}> <BaseModal
onEscapeKeyDown={(e) => {
e.preventDefault();
if (code === value) {
setOpen(false);
} else {
if (
!(
codeRef.current?.editor.completer.popup &&
codeRef.current?.editor.completer.popup.isOpen
)
) {
setOpenConfirmation(true);
}
}
}}
open={open}
setOpen={setOpen}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}> <BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
<span className="pr-2"> {EDIT_CODE_TITLE} </span> <span className="pr-2"> {EDIT_CODE_TITLE} </span>
@ -168,6 +192,7 @@ export default function CodeAreaModal({
<div className="flex h-full w-full flex-col transition-all"> <div className="flex h-full w-full flex-col transition-all">
<div className="h-full w-full"> <div className="h-full w-full">
<AceEditor <AceEditor
ref={codeRef}
readOnly={readonly} readOnly={readonly}
value={code} value={code}
mode="python" mode="python"
@ -209,7 +234,7 @@ export default function CodeAreaModal({
<div className="flex h-fit w-full justify-end"> <div className="flex h-fit w-full justify-end">
<Button <Button
className="mt-3" className="mt-3"
onClick={handleClick} onClick={processCode}
type="submit" type="submit"
id="checkAndSaveBtn" id="checkAndSaveBtn"
disabled={readonly} disabled={readonly}
@ -218,6 +243,28 @@ export default function CodeAreaModal({
</Button> </Button>
</div> </div>
</div> </div>
<ConfirmationModal
onClose={setOpenConfirmation}
onEscapeKeyDown={(e) => {
e.stopPropagation();
setOpenConfirmation(false);
}}
size="x-small"
icon="AlertTriangle"
confirmationText="Check & Save"
cancelText="Discard Changes"
open={openConfirmation}
onCancel={() => setOpen(false)}
onConfirm={() => {
processCode();
setOpenConfirmation(false);
}}
title="Caution"
>
<ConfirmationModal.Content>
<p>Are you sure you want to exit without saving your changes?</p>
</ConfirmationModal.Content>
</ConfirmationModal>
</BaseModal.Content> </BaseModal.Content>
</BaseModal> </BaseModal>
); );

View file

@ -1,3 +1,4 @@
import GenericIconComponent from "@/components/genericIconComponent";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ShadTooltip from "../../components/shadTooltipComponent"; import ShadTooltip from "../../components/shadTooltipComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
@ -38,12 +39,11 @@ function ConfirmationModal({
data, data,
index, index,
onConfirm, onConfirm,
size,
open, open,
onClose, onClose,
onCancel, onCancel,
...props
}: ConfirmationModalType) { }: ConfirmationModalType) {
const Icon: any = nodeIconsLucide[icon];
const [modalOpen, setModalOpen] = useState(open ?? false); const [modalOpen, setModalOpen] = useState(open ?? false);
const [flag, setFlag] = useState(false); const [flag, setFlag] = useState(false);
@ -67,12 +67,12 @@ function ConfirmationModal({
); );
return ( return (
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}> <BaseModal {...props} open={open} setOpen={setModalOpen}>
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger> <BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader ?? null}> <BaseModal.Header description={titleHeader ?? null}>
<span className="pr-2">{title}</span> <span className="pr-2">{title}</span>
<Icon <GenericIconComponent
name="icon" name={icon}
className="h-6 w-6 pl-1 text-foreground" className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true" aria-hidden="true"
/> />

View file

@ -457,6 +457,7 @@ export type ConfirmationModalType = {
| "large-h-full" | "large-h-full"
| "small-h-full" | "small-h-full"
| "medium-h-full"; | "medium-h-full";
onEscapeKeyDown?: (e: KeyboardEvent) => void;
}; };
export type UserManagementType = { export type UserManagementType = {