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:
parent
7ce6a9ee03
commit
e5ee0ba946
7 changed files with 186 additions and 140 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
106
src/frontend/src/modals/textAreaModal/index.tsx
Normal file
106
src/frontend/src/modals/textAreaModal/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue