refactor: Add ComponentTextModal and Prompt modal for displaying and editing text areas and components (#3346)

* add separated component for prompt modal

* refactor: rename promptAreaModal to promptModal and update type import

* refactor: update promptComponent to use PromptModal instead of GenericModal

* refactor: update textarea-primary class in applies.css

* refactor: add ComponentTextModal for displaying and editing text areas

Add a new component, ComponentTextModal, for displaying and editing text areas. This component includes a textarea input, a title, and an icon. It also supports password visibility toggling. The ComponentTextModal is used within a BaseModal component and includes a save button for finishing the editing process. This commit refactors the code to add the ComponentTextModal and its related functionality.

* refactor: Add ComponentTextModal for displaying and editing text areas

* delete genericModal component and their references

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
anovazzi1 2024-08-14 15:08:59 -03:00 committed by GitHub
commit e5ee0ba946
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 186 additions and 140 deletions

View file

@ -1,7 +1,5 @@
import PromptModal from "@/modals/promptModal";
import { useEffect } from "react";
import { TypeModal } from "../../constants/enums";
import GenericModal from "../../modals/genericModal";
import { PromptAreaComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
@ -25,14 +23,11 @@ export default function PromptAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full" : "w-full"}>
<GenericModal
<PromptModal
id={id}
field_name={field_name}
readonly={readonly}
type={TypeModal.PROMPT}
value={value}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={onChange}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
@ -63,7 +58,7 @@ export default function PromptAreaComponent({
)}
</div>
</Button>
</GenericModal>
</PromptModal>
</div>
);
}

View file

@ -1,8 +1,6 @@
import ComponentTextModal from "@/modals/textAreaModal";
import { classNames } from "@/utils/utils";
import { useEffect } from "react";
import { EDIT_TEXT_MODAL_TITLE } from "../../constants/constants";
import { TypeModal } from "../../constants/enums";
import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
@ -47,11 +45,8 @@ export default function TextAreaComponent({
onChange(event.target.value);
}}
/>
<GenericModal
<ComponentTextModal
changeVisibility={updateVisibility}
type={TypeModal.TEXT}
buttonText="Finish Editing"
modalTitle={EDIT_TEXT_MODAL_TITLE}
value={value}
setValue={(value: string) => {
onChange(value);
@ -76,7 +71,7 @@ export default function TextAreaComponent({
/>
</Button>
</div>
</GenericModal>
</ComponentTextModal>
{password !== undefined && (
<Button
unstyled

View file

@ -16,45 +16,34 @@ import {
INVALID_CHARACTERS,
MAX_WORDS_HIGHLIGHT,
PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE,
regexHighlight,
} from "../../constants/constants";
import { TypeModal } from "../../constants/enums";
import { postValidatePrompt } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { genericModalPropsType } from "../../types/components";
import { PromptModalType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import BaseModal from "../baseModal";
import varHighlightHTML from "./utils/var-highlight-html";
export default function GenericModal({
export default function PromptModal({
field_name = "",
value,
setValue,
buttonText,
modalTitle,
type,
nodeClass,
setNodeClass,
children,
disabled,
id = "",
readonly = false,
password,
changeVisibility,
}: genericModalPropsType): JSX.Element {
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
}: PromptModalType): JSX.Element {
const [modalOpen, setModalOpen] = useState(false);
const [myModalType] = useState(type);
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlight, setWordsHighlight] = useState<Set<string>>(new Set());
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const textRef = useRef<HTMLTextAreaElement>(null);
const divRef = useRef(null);
const divRefPrompt = useRef(null);
@ -111,10 +100,10 @@ export default function GenericModal({
.replace(/\n/g, "<br />");
useEffect(() => {
if (type === TypeModal.PROMPT && inputValue && inputValue != "") {
if (inputValue && inputValue != "") {
checkVariables(inputValue);
}
}, [inputValue, type]);
}, [inputValue]);
useEffect(() => {
if (typeof value === "string") setInputValue(value);
@ -189,52 +178,23 @@ export default function GenericModal({
<BaseModal.Trigger disable={disabled} asChild>
{children}
</BaseModal.Trigger>
<BaseModal.Header
description={(() => {
switch (myModalTitle) {
case "Edit Text":
return TEXT_DIALOG_SUBTITLE;
case "Edit Prompt":
return PROMPT_DIALOG_SUBTITLE;
default:
return null;
}
})()}
>
<BaseModal.Header description={PROMPT_DIALOG_SUBTITLE}>
<div className="flex w-full items-start gap-3">
<div className="flex">
<span className="pr-2" data-testid="modal-title">
{myModalTitle}
Edit Prompt
</span>
<IconComponent
name={
myModalTitle === "Edit Prompt" ? "TerminalSquare" : "FileText"
}
name="TerminalSquare"
className="h-6 w-6 pl-1 text-primary"
aria-hidden="true"
/>
</div>
{password !== undefined && (
<div>
<button
onClick={() => {
if (changeVisibility) changeVisibility();
}}
>
<IconComponent
name={password ? "Eye" : "EyeOff"}
className="h-6 w-6 cursor-pointer text-primary"
/>
</button>
</div>
)}
</div>
</BaseModal.Header>
<BaseModal.Content overflowHidden>
<div className={classNames("flex h-full w-full rounded-lg border")}>
{type === TypeModal.PROMPT && isEdit && !readonly ? (
{isEdit && !readonly ? (
<Textarea
id={"modal-" + id}
data-testid={"modal-" + id}
@ -254,107 +214,75 @@ export default function GenericModal({
handleKeyDown(e, inputValue, "");
}}
/>
) : type === TypeModal.PROMPT && (!isEdit || readonly) ? (
) : (
<SanitizedHTMLWrapper
className={getClassByNumberLength() + " bg-muted"}
content={coloredContent}
onClick={() => {
setIsEdit(true);
if (!readonly) setIsEdit(true);
}}
suppressWarning={true}
/>
) : type !== TypeModal.PROMPT ? (
<Textarea
password={password}
ref={textRef}
className="form-input h-full w-full resize-none overflow-auto rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
placeholder={EDIT_TEXT_PLACEHOLDER}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
readOnly={readonly}
id={"text-area-modal"}
data-testid={"text-area-modal"}
/>
) : (
<></>
)}
</div>
</BaseModal.Content>
<BaseModal.Footer>
<div className="flex w-full shrink-0 items-end justify-between">
<div className="mb-auto flex-1">
{type === TypeModal.PROMPT && (
<div className="mr-2">
<div
ref={divRef}
className="max-h-20 overflow-y-auto custom-scroll"
>
<div className="flex flex-wrap items-center gap-2">
<IconComponent
name="Braces"
className="flex h-4 w-4 text-primary"
/>
<span className="text-md font-semibold text-primary">
Prompt Variables:
</span>
<div className="mr-2">
<div
ref={divRef}
className="max-h-20 overflow-y-auto custom-scroll"
>
<div className="flex flex-wrap items-center gap-2">
<IconComponent
name="Braces"
className="flex h-4 w-4 text-primary"
/>
<span className="text-md font-semibold text-primary">
Prompt Variables:
</span>
{Array.from(wordsHighlight).map((word, index) => (
<ShadTooltip
{Array.from(wordsHighlight).map((word, index) => (
<ShadTooltip
key={index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
<Badge
key={index}
content={word.replace(/[{}]/g, "")}
asChild={false}
variant="gray"
size="md"
className="max-w-[40vw] cursor-default truncate p-1 text-sm"
>
<Badge
key={index}
variant="gray"
size="md"
className="max-w-[40vw] cursor-default truncate p-1 text-sm"
>
<div className="relative bottom-[1px]">
<span id={"badge" + index.toString()}>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
<div className="relative bottom-[1px]">
<span id={"badge" + index.toString()}>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
<span className="mt-2 text-xs text-muted-foreground">
Prompt variables can be created with any chosen name inside
curly brackets, e.g. {"{variable_name}"}
</span>
</div>
)}
<span className="mt-2 text-xs text-muted-foreground">
Prompt variables can be created with any chosen name inside
curly brackets, e.g. {"{variable_name}"}
</span>
</div>
</div>
<Button
data-testid="genericModalBtnSave"
id="genericModalBtnSave"
disabled={readonly}
onClick={() => {
switch (myModalType) {
case TypeModal.TEXT:
setValue(inputValue);
setModalOpen(false);
break;
case TypeModal.PROMPT:
validatePrompt(false);
break;
default:
break;
}
validatePrompt(false);
}}
type="submit"
>
{myButtonText}
Check & Save
</Button>
</div>
</BaseModal.Footer>

View file

@ -0,0 +1,106 @@
import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Textarea } from "../../components/ui/textarea";
import {
EDIT_TEXT_PLACEHOLDER,
TEXT_DIALOG_SUBTITLE,
} from "../../constants/constants";
import { textModalPropsType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import BaseModal from "../baseModal";
export default function ComponentTextModal({
value,
setValue,
children,
disabled,
readonly = false,
password,
changeVisibility,
}: textModalPropsType): JSX.Element {
const [modalOpen, setModalOpen] = useState(false);
const [inputValue, setInputValue] = useState(value);
const textRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (typeof value === "string") setInputValue(value);
}, [value, modalOpen]);
return (
<BaseModal
onChangeOpenModal={(open) => {}}
open={modalOpen}
setOpen={setModalOpen}
>
<BaseModal.Trigger disable={disabled} asChild>
{children}
</BaseModal.Trigger>
<BaseModal.Header description={TEXT_DIALOG_SUBTITLE}>
<div className="flex w-full items-start gap-3">
<div className="flex">
<span className="pr-2" data-testid="modal-title">
{TEXT_DIALOG_SUBTITLE}
</span>
<IconComponent
name={"FileText"}
className="h-6 w-6 pl-1 text-primary"
aria-hidden="true"
/>
</div>
{password !== undefined && (
<div>
<button
onClick={() => {
if (changeVisibility) changeVisibility();
}}
>
<IconComponent
name={password ? "Eye" : "EyeOff"}
className="h-6 w-6 cursor-pointer text-primary"
/>
</button>
</div>
)}
</div>
</BaseModal.Header>
<BaseModal.Content overflowHidden>
<div className={classNames("flex h-full w-full rounded-lg border")}>
<Textarea
password={password}
ref={textRef}
className="form-input h-full w-full resize-none overflow-auto rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
placeholder={EDIT_TEXT_PLACEHOLDER}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
readOnly={readonly}
id={"text-area-modal"}
data-testid={"text-area-modal"}
/>
</div>
</BaseModal.Content>
<BaseModal.Footer>
<div className="flex w-full shrink-0 items-end justify-end">
<Button
data-testid="genericModalBtnSave"
id="genericModalBtnSave"
disabled={readonly}
onClick={() => {
setValue(inputValue);
setModalOpen(false);
}}
type="submit"
>
Finish Editing
</Button>
</div>
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -196,7 +196,7 @@
/* The same as primary-input but no-truncate */
.textarea-primary {
@apply form-input block w-full rounded-md border-border bg-muted px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
@apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
}
.input-edit-node {

View file

@ -700,6 +700,28 @@ export type genericModalPropsType = {
changeVisibility?: () => void;
};
export type PromptModalType = {
field_name?: string;
setValue: (value: string) => void;
value: string;
disabled?: boolean;
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType, type?: string) => void;
children: ReactNode;
id?: string;
readonly?: boolean;
};
export type textModalPropsType = {
setValue: (value: string) => void;
value: string;
disabled?: boolean;
children: ReactNode;
readonly?: boolean;
password?: boolean;
changeVisibility?: () => void;
};
export type newFlowModalPropsType = {
open: boolean;
setOpen: (open: boolean) => void;