refactor: improve parameter render component (#3995)
* refactor: update dynamic parameter in ParameterRenderComponent to false * refactor: update CodeAreaComponent to use CodeAreaModal for node toolbar * feat: Add EmptyParameterComponent to render empty parameters * refactor: Add types for parameter render component Add typescript types for the parameter render component to improve code readability and maintainability. * feat: Add EmptyParameterComponent to render empty parameters * refactor: Update RefreshParameterComponent to use InputProps Update the RefreshParameterComponent to use the InputProps interface for better code readability and maintainability. * refactor: Update InputProps in parameterRenderComponent/types.ts Update the InputProps interface in parameterRenderComponent/types.ts to remove the Partial<OptionalInputProps> and simplify the composition of input props. This improves code readability and maintainability. * refactor: Update InputProps in parameterRenderComponent/types.ts * refactor: Update parameterRenderComponent/types.ts to use handleOnNewValue instead of onChange Update the parameterRenderComponent/types.ts file to import the handleOnNewValueType from "@/CustomNodes/hooks/use-handle-new-value" and replace the onChange function with handleOnNewValue in the BaseInputProps interface. This change improves code readability and maintainability. * update folder structure * refactor: Update TableModal to support disabled state Update the TableModal component to include a disabled prop, allowing the modal to be disabled when necessary. This improves the flexibility and usability of the component. * refactor: Update parameterRenderComponent/types.ts to include TableComponentType Update the parameterRenderComponent/types.ts file to include the TableComponentType interface, which defines the structure for a table component. This change improves code organization and maintainability. * refactor: Update parameterRenderComponent to use TableNodeComponent from the correct folder * refactor: Update codeAreaComponent imports and props in parameterRenderComponent Update the imports and props in the parameterRenderComponent file to reflect the changes made in the codeAreaComponent file. This refactor improves code organization and maintainability. * refactor: Update parameterRenderComponent/types.ts to include CodeAreaComponentType Update the parameterRenderComponent/types.ts file to include the CodeAreaComponentType interface, which defines the structure for a code area component. This change improves code organization and maintainability. * refactor: Remove unused import of CodeAreaComponent in nodeToolbarComponent Remove the unused import of CodeAreaComponent in the nodeToolbarComponent file to improve code cleanliness and maintainability. * refactor: Remove unused import of CodeAreaComponent in nodeToolbarComponent * refactor: Remove unused import of CodeAreaComponent in nodeToolbarComponent * refactor: Remove unused import of CodeAreaComponent in codeAreaComponent/index.tsx * refactor: Remove unused import of CodeAreaComponent in parameterRenderComponent/index.tsx * refactor: Update floatComponent imports and props in parameterRenderComponent Update the imports and props in the parameterRenderComponent file to reflect the changes made in the floatComponent file. This refactor improves code organization and maintainability. * refactor: Update floatComponent imports and props in parameterRenderComponent * update int component path * update int component type * update toogle location * refactor: Update ToggleComponentType in components/index.ts Remove the ToggleComponentType interface from the components/index.ts file. This change is part of a refactoring effort to remove unused code and improve code organization. * refactor: Update ToggleComponentType in components/index.ts Remove unused ToggleComponentType interface from components/index.ts * refactor: Update ToggleShadComponent to handle new value changes Refactor the ToggleShadComponent to handle new value changes by passing the updated value to the handleOnNewValue function. This improves the functionality and maintainability of the component. * refactor: Move InputFileComponent to parameterRenderComponent/components folder Move the InputFileComponent file from the src/frontend/src/components folder to the src/frontend/src/components/parameterRenderComponent/components folder. This change improves code organization and maintainability. * refactor: Add FileComponentType interface to parameterRenderComponent/types.ts Add the FileComponentType interface to the parameterRenderComponent/types.ts file. This interface defines the fileTypes property for the FileComponentType, allowing for better type checking and documentation. This change improves code organization and maintainability. * refactor: Update InputFileComponent props in parameterRenderComponent Update the props of the InputFileComponent in the parameterRenderComponent file to match the changes made in the index.tsx file. This refactor improves code organization and maintainability. * refactor: Move PromptAreaComponent to parameterRenderComponent/components folder * update types prompt field * refactor: Update PromptAreaComponent to handle new value changes Refactor the PromptAreaComponent to handle new value changes by passing the updated value to the handleOnNewValue function. This improves the functionality and maintainability of the component. * refactor: Move LinkComponent to parameterRenderComponent/components folder * refactor: Add LinkComponentType interface to parameterRenderComponent/types.ts Add the LinkComponentType interface to the parameterRenderComponent/types.ts file. This interface defines the icon and text properties for the LinkComponentType, allowing for better type checking and documentation. This change improves code organization and maintainability. * refactor: Update LinkComponent to use InputProps in parameterRenderComponent Update the LinkComponent in parameterRenderComponent to use the InputProps interface for better type checking and consistency. This refactor improves code organization and maintainability. * refactor: Move KeypairListComponent to parameterRenderComponent/components folder * refactor: Add KeyPairListComponentType interface to parameterRenderComponent/types.ts * refactor: Update KeypairListComponent to use InputProps in parameterRenderComponent Refactor the KeypairListComponent in parameterRenderComponent to use the InputProps interface for better type checking and consistency. This refactor improves code organization and maintainability. * refactor: Move DictComponent to parameterRenderComponent/components folder * refactor: Update KeyPairListComponentType interface in parameterRenderComponent/types.ts * refactor: Update KeypairListComponent to use InputProps in parameterRenderComponent * refactor: Move InputListComponent to parameterRenderComponent/components folder Move the InputListComponent to the parameterRenderComponent/components folder for better code organization and maintainability. * refactor: Add StrRenderComponentType interface to parameterRenderComponent/types.ts * refactor: Move InputListComponent to parameterRenderComponent/components folder * refactor: Move InputListComponent to parameterRenderComponent/components folder * refactor: Move InputListComponent to parameterRenderComponent/components folder Move the InputListComponent to the parameterRenderComponent/components folder for better code organization and maintainability. * refactor: Update InputListComponent to use InputProps in parameterRenderComponent Refactor the InputListComponent in parameterRenderComponent to use the InputProps interface for better type checking and consistency. This refactor improves code organization and maintainability. * refactor: Move IOFieldView to IOModal/components folder Move the IOFieldView component to the IOModal/components folder for better code organization and maintainability. * refactor: Move DropdownComponent to parameterRenderComponent/components folder * refactor: Add DropDownComponentType interface to parameterRenderComponent/types.ts * refactor: Update DropdownComponent import in StrRenderComponent Update the import statement for DropdownComponent in StrRenderComponent to reflect its new location in the parameterRenderComponent/components folder. This refactor improves code organization and maintainability. * refactor: Update StrRenderComponent to use baseInputProps Refactor the StrRenderComponent in parameterRenderComponent to use the baseInputProps object instead of individual props for better code organization and maintainability. * refactor: Update StrRenderComponent to use TextAreaComponent from correct location Update the import statement for TextAreaComponent in StrRenderComponent to reflect its new location in the parameterRenderComponent/components folder. This refactor improves code organization and maintainability. * remove unused imports * refactor: Add TextAreaComponentType interface to parameterRenderComponent/types.ts * refactor: Update TextAreaComponent to use handleOnNewValue instead of onChange Refactor the TextAreaComponent in parameterRenderComponent to use the handleOnNewValue function instead of the onChange function for better code consistency. This change aligns with the recent updates to the component's props and improves maintainability. * refactor: Update StrRenderComponent to use baseInputProps Refactor the StrRenderComponent in parameterRenderComponent to use the baseInputProps object instead of individual props for better code organization and maintainability. * refactor: Move inputGlobalComponent to parameterRenderComponent/components folder Move the inputGlobalComponent to the parameterRenderComponent/components folder to improve code organization and maintainability. * refactor: Move inputGlobalComponent to parameterRenderComponent/components folder * refactor: Update InputGlobalComponent to use handleOnNewValue instead of onChange Refactor the InputGlobalComponent in parameterRenderComponent to use the handleOnNewValue function instead of the onChange function for better code consistency. This change aligns with the recent updates to the component's props and improves maintainability. * refactor: Update StrRenderComponent to use baseInputProps Refactor the StrRenderComponent in parameterRenderComponent to use the baseInputProps object instead of individual props for better code organization and maintainability. * refactor: Move multiselectComponent to parameterRenderComponent/components folder Move the multiselectComponent to the parameterRenderComponent/components folder to improve code organization and maintainability. * refactor: Move MultiselectComponent to parameterRenderComponent/components folder Move the MultiselectComponent to the parameterRenderComponent/components folder to improve code organization and maintainability. * refactor: Move MultiselectComponent to parameterRenderComponent/components folder * refactor: Move MultiselectComponent to parameterRenderComponent/components folder * refactor: Remove unused code in StrRenderComponent * refactor: Remove unused code in StrRenderComponent * refactor: Remove unused code in StrRenderComponent * [autofix.ci] apply automated fixes * ✨ (tableInputComponent.spec.ts): add ua-parser-js library to parse user agent information for better control handling based on OS 🔧 (tableInputComponent.spec.ts): update key press event to use the correct control key based on the user's operating system for textarea selection. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
parent
94a647f60d
commit
12a381a3fa
31 changed files with 626 additions and 635 deletions
|
|
@ -2,7 +2,7 @@ import { PopoverAnchor } from "@radix-ui/react-popover";
|
|||
import Fuse from "fuse.js";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { DropDownComponentType } from "../../types/components";
|
||||
import { DropDownComponent } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { default as ForwardedIconComponent } from "../genericIconComponent";
|
||||
import ShadTooltip from "../shadTooltipComponent";
|
||||
|
|
@ -31,7 +31,7 @@ export default function Dropdown({
|
|||
editNode = false,
|
||||
id = "",
|
||||
children,
|
||||
}: DropDownComponentType): JSX.Element {
|
||||
}: DropDownComponent): JSX.Element {
|
||||
const [open, setOpen] = useState(children ? true : false);
|
||||
|
||||
const refButton = useRef<HTMLButtonElement>(null);
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
import { handleOnNewValueType } from "@/CustomNodes/hooks/use-handle-new-value";
|
||||
import { InputFieldType } from "@/types/api";
|
||||
import Dropdown from "../../../dropdownComponent";
|
||||
import InputGlobalComponent from "../../../inputGlobalComponent";
|
||||
import InputListComponent from "../../../inputListComponent";
|
||||
import MultiselectComponent from "../../../multiselectComponent";
|
||||
import TextAreaComponent from "../../../textAreaComponent";
|
||||
|
||||
export function StrRenderComponent({
|
||||
templateData,
|
||||
value,
|
||||
name,
|
||||
disabled,
|
||||
handleOnNewValue,
|
||||
editNode,
|
||||
id,
|
||||
}: {
|
||||
templateData: Partial<InputFieldType>;
|
||||
value: any;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
handleOnNewValue: handleOnNewValueType;
|
||||
editNode: boolean;
|
||||
id: string;
|
||||
}) {
|
||||
const onChange = (value: any, dbValue?: boolean, skipSnapshot?: boolean) => {
|
||||
handleOnNewValue({ value, load_from_db: dbValue }, { skipSnapshot });
|
||||
};
|
||||
|
||||
if (!templateData.options) {
|
||||
return templateData?.list ? (
|
||||
<InputListComponent
|
||||
componentName={name ?? undefined}
|
||||
editNode={editNode}
|
||||
disabled={disabled}
|
||||
value={value || [""]}
|
||||
onChange={onChange}
|
||||
id={`inputlist_${id}`}
|
||||
/>
|
||||
) : templateData.multiline ? (
|
||||
<TextAreaComponent
|
||||
password={templateData.password}
|
||||
updateVisibility={() => {
|
||||
if (templateData.password !== undefined) {
|
||||
handleOnNewValue(
|
||||
{ password: !templateData.password },
|
||||
{ skipSnapshot: true },
|
||||
);
|
||||
}
|
||||
}}
|
||||
id={`textarea_${id}`}
|
||||
disabled={disabled}
|
||||
editNode={editNode}
|
||||
value={value ?? ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
) : (
|
||||
<InputGlobalComponent
|
||||
disabled={disabled}
|
||||
editNode={editNode}
|
||||
onChange={onChange}
|
||||
name={name}
|
||||
data={templateData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!!templateData.options && !!templateData?.list) {
|
||||
return (
|
||||
<MultiselectComponent
|
||||
editNode={editNode}
|
||||
disabled={disabled}
|
||||
options={
|
||||
(Array.isArray(templateData.options)
|
||||
? templateData.options
|
||||
: [templateData.options]) || []
|
||||
}
|
||||
combobox={templateData.combobox}
|
||||
value={
|
||||
(Array.isArray(templateData.value)
|
||||
? templateData.value
|
||||
: [templateData.value]) || []
|
||||
}
|
||||
id={`multiselect_${id}`}
|
||||
onSelect={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!!templateData.options) {
|
||||
return (
|
||||
<Dropdown
|
||||
editNode={editNode}
|
||||
options={templateData.options}
|
||||
onSelect={onChange}
|
||||
combobox={templateData.combobox}
|
||||
value={value || ""}
|
||||
id={`dropdown_${id}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
import TableModal from "@/modals/tableModal";
|
||||
import { FormatColumns, generateBackendColumnsFromValue } from "@/utils/utils";
|
||||
import { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community";
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { ForwardedIconComponent } from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { InputProps, TableComponentType } from "../../types";
|
||||
|
||||
export default function TableNodeComponent({
|
||||
tableTitle,
|
||||
description,
|
||||
value,
|
||||
editNode = false,
|
||||
id = "",
|
||||
columns,
|
||||
handleOnNewValue,
|
||||
disabled = false,
|
||||
}: InputProps<any[], TableComponentType>): JSX.Element {
|
||||
const dataTypeDefinitions: {
|
||||
[cellDataType: string]: DataTypeDefinition<any>;
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
// override `date` to handle custom date format `dd/mm/yyyy`
|
||||
date: {
|
||||
baseDataType: "date",
|
||||
extendsDataType: "date",
|
||||
valueParser: (params) => {
|
||||
if (params.newValue == null) {
|
||||
return null;
|
||||
}
|
||||
// convert from `dd/mm/yyyy`
|
||||
const dateParts = params.newValue.split("/");
|
||||
return dateParts.length === 3
|
||||
? new Date(
|
||||
parseInt(dateParts[2]),
|
||||
parseInt(dateParts[1]) - 1,
|
||||
parseInt(dateParts[0]),
|
||||
)
|
||||
: null;
|
||||
},
|
||||
valueFormatter: (params) => {
|
||||
let date = params.value;
|
||||
if (typeof params.value === "string") {
|
||||
date = new Date(params.value);
|
||||
}
|
||||
// convert to `dd/mm/yyyy`
|
||||
return date == null
|
||||
? ""
|
||||
: `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
|
||||
},
|
||||
},
|
||||
number: {
|
||||
baseDataType: "number",
|
||||
extendsDataType: "number",
|
||||
valueFormatter: (params) =>
|
||||
params.value == null ? "" : `${params.value}`,
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
const [selectedNodes, setSelectedNodes] = useState<Array<any>>([]);
|
||||
const agGrid = useRef<AgGridReact>(null);
|
||||
const componentColumns = columns
|
||||
? columns
|
||||
: generateBackendColumnsFromValue(value ?? []);
|
||||
const AgColumns = FormatColumns(componentColumns);
|
||||
function setAllRows() {
|
||||
if (agGrid.current && !agGrid.current.api.isDestroyed()) {
|
||||
const rows: any = [];
|
||||
agGrid.current.api.forEachNode((node) => rows.push(node.data));
|
||||
handleOnNewValue({ value: rows });
|
||||
}
|
||||
}
|
||||
function deleteRow() {
|
||||
if (agGrid.current && selectedNodes.length > 0) {
|
||||
agGrid.current.api.applyTransaction({
|
||||
remove: selectedNodes.map((node) => node.data),
|
||||
});
|
||||
setSelectedNodes([]);
|
||||
setAllRows();
|
||||
}
|
||||
}
|
||||
function duplicateRow() {
|
||||
if (agGrid.current && selectedNodes.length > 0) {
|
||||
const toDuplicate = selectedNodes.map((node) => cloneDeep(node.data));
|
||||
setSelectedNodes([]);
|
||||
const rows: any = [];
|
||||
handleOnNewValue({ value: [...value, ...toDuplicate] });
|
||||
}
|
||||
}
|
||||
function addRow() {
|
||||
const newRow = {};
|
||||
componentColumns.forEach((column) => {
|
||||
newRow[column.name] = null;
|
||||
});
|
||||
handleOnNewValue({ value: [...value, newRow] });
|
||||
}
|
||||
|
||||
function updateComponent() {
|
||||
setAllRows();
|
||||
}
|
||||
const editable = componentColumns.map((column) => {
|
||||
const isCustomEdit =
|
||||
column.formatter &&
|
||||
(column.formatter === "text" || column.formatter === "json");
|
||||
return {
|
||||
field: column.name,
|
||||
onUpdate: updateComponent,
|
||||
editableCell: isCustomEdit ? false : true,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"flex w-full items-center" + (disabled ? " cursor-not-allowed" : "")
|
||||
}
|
||||
>
|
||||
<div className="flex w-full items-center gap-3" data-testid={"div-" + id}>
|
||||
<TableModal
|
||||
dataTypeDefinitions={dataTypeDefinitions}
|
||||
autoSizeStrategy={{ type: "fitGridWidth", defaultMinWidth: 100 }}
|
||||
tableTitle={tableTitle}
|
||||
description={description}
|
||||
ref={agGrid}
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedNodes(event.api.getSelectedNodes());
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
editable={editable}
|
||||
pagination={true}
|
||||
addRow={addRow}
|
||||
onDelete={deleteRow}
|
||||
onDuplicate={duplicateRow}
|
||||
displayEmptyAlert={false}
|
||||
className="h-full w-full"
|
||||
columnDefs={AgColumns}
|
||||
rowData={value}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="primary"
|
||||
size={editNode ? "xs" : "default"}
|
||||
className={
|
||||
"w-full " +
|
||||
(disabled ? "pointer-events-none cursor-not-allowed" : "")
|
||||
}
|
||||
>
|
||||
<ForwardedIconComponent name="Table" className="mt-px h-4 w-4" />
|
||||
<span className="font-normal">Open Table</span>
|
||||
</Button>
|
||||
</TableModal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import CodeAreaModal from "../../../../modals/codeAreaModal";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { InputProps } from "../../types";
|
||||
|
||||
export default function CodeAreaComponent({
|
||||
value,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
editNode = false,
|
||||
nodeClass,
|
||||
handleNodeClass,
|
||||
id = "",
|
||||
}: InputProps<string>) {
|
||||
const [componentValue, setComponentValue] = useState(
|
||||
typeof value == "string" ? value : JSON.stringify(value),
|
||||
);
|
||||
useEffect(() => {
|
||||
if (disabled && componentValue !== "") {
|
||||
setComponentValue("");
|
||||
handleOnNewValue({ value: "" }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
setComponentValue(typeof value == "string" ? value : JSON.stringify(value));
|
||||
}, [value]);
|
||||
|
||||
const handleValueChange = (newValue) => {
|
||||
handleOnNewValue({ value: newValue });
|
||||
};
|
||||
|
||||
const renderInputText = () => (
|
||||
<span
|
||||
id={id}
|
||||
data-testid={id}
|
||||
className={cn(
|
||||
editNode
|
||||
? "input-edit-node input-dialog"
|
||||
: "primary-input text-muted-foreground",
|
||||
disabled && !editNode && "input-disable input-ring",
|
||||
)}
|
||||
>
|
||||
{value !== "" ? value : "Type something..."}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderExternalLinkIcon = () => {
|
||||
if (editNode) return null;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
name="ExternalLink"
|
||||
className={cn(
|
||||
"icons-parameters-comp shrink-0",
|
||||
disabled ? "text-ring" : "hover:text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("w-full", disabled && "pointer-events-none")}>
|
||||
<CodeAreaModal
|
||||
dynamic={false}
|
||||
value={value}
|
||||
nodeClass={nodeClass}
|
||||
setNodeClass={handleNodeClass!}
|
||||
setValue={handleValueChange}
|
||||
>
|
||||
<div className="flex w-full items-center gap-3">
|
||||
{renderInputText()}
|
||||
{renderExternalLinkIcon()}
|
||||
</div>
|
||||
</CodeAreaModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import DictAreaModal from "../../../../modals/dictAreaModal";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import ForwardedIconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { InputProps } from "../../types";
|
||||
|
||||
export default function DictComponent({
|
||||
value = [],
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
editNode = false,
|
||||
id = "",
|
||||
}: InputProps<object | object[] | string>): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
handleOnNewValue({ value: {} }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"flex w-full flex-col gap-3",
|
||||
disabled ? "pointer-events-none" : "",
|
||||
)}
|
||||
>
|
||||
{
|
||||
<div className="flex w-full gap-3" data-testid={id}>
|
||||
<DictAreaModal
|
||||
value={(value || "").toString() === "{}" ? {} : value}
|
||||
onChange={(obj) => {
|
||||
handleOnNewValue({ value: obj });
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"w-full font-normal",
|
||||
editNode ? "h-fit px-3 py-0.5" : "",
|
||||
)}
|
||||
data-testid="dict-input"
|
||||
>
|
||||
<ForwardedIconComponent name="BookMarked" className="h-4 w-4" />
|
||||
Edit Dictionary
|
||||
</Button>
|
||||
</DictAreaModal>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import Dropdown from "../../../dropdownComponent";
|
||||
import { DropDownComponentType, InputProps } from "../../types";
|
||||
|
||||
export default function DropdownComponent({
|
||||
id,
|
||||
value,
|
||||
editNode,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
combobox,
|
||||
options,
|
||||
}: InputProps<string, DropDownComponentType>) {
|
||||
const onChange = (value: any, dbValue?: boolean, skipSnapshot?: boolean) => {
|
||||
handleOnNewValue({ value, load_from_db: dbValue }, { skipSnapshot });
|
||||
};
|
||||
return (
|
||||
<Dropdown
|
||||
disabled={disabled}
|
||||
editNode={editNode}
|
||||
options={options}
|
||||
onSelect={onChange}
|
||||
combobox={combobox}
|
||||
value={value || ""}
|
||||
id={`dropdown_${id}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { InputProps } from "../../types";
|
||||
|
||||
export function EmptyParameterComponent({
|
||||
id,
|
||||
value,
|
||||
editNode,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
}: InputProps) {
|
||||
return <div id={id}>{String(value)}</div>;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { useEffect } from "react";
|
||||
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
||||
import { Input } from "../../../ui/input";
|
||||
import { FloatComponentType, InputProps } from "../../types";
|
||||
|
||||
export default function FloatComponent({
|
||||
value,
|
||||
disabled,
|
||||
rangeSpec,
|
||||
editNode = false,
|
||||
handleOnNewValue,
|
||||
}: InputProps<string, FloatComponentType>): JSX.Element {
|
||||
const step = rangeSpec?.step ?? 0.1;
|
||||
const min = rangeSpec?.min ?? -2;
|
||||
const max = rangeSpec?.max ?? 2;
|
||||
// Clear component state
|
||||
useEffect(() => {
|
||||
if (disabled && value !== "") {
|
||||
handleOnNewValue({ value: "" }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = Number(event.target.value);
|
||||
if (inputValue < min) {
|
||||
event.target.value = min.toString();
|
||||
} else if (inputValue > max) {
|
||||
event.target.value = max.toString();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
handleOnNewValue({ value: Number(event.target.value) });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="float-input"
|
||||
data-testid="float-input"
|
||||
type="number"
|
||||
step={step}
|
||||
min={min}
|
||||
max={max}
|
||||
value={value ?? ""}
|
||||
disabled={disabled}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder="Enter a value"
|
||||
onInput={handleInput}
|
||||
onChange={handleChange}
|
||||
onKeyDown={(e) => handleKeyDown(e, value, "")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||
import useFileSizeValidator from "@/shared/hooks/use-file-size-validator";
|
||||
import { useUtilityStore } from "@/stores/utilityStore";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
CONSOLE_ERROR_MSG,
|
||||
INVALID_FILE_ALERT,
|
||||
INVALID_FILE_SIZE_ALERT,
|
||||
} from "../../../../constants/alerts_constants";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { FileComponentType, InputProps } from "../../types";
|
||||
|
||||
export default function InputFileComponent({
|
||||
value,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
fileTypes,
|
||||
editNode = false,
|
||||
id,
|
||||
}: InputProps<string, FileComponentType>): JSX.Element {
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const { validateFileSize } = useFileSizeValidator(setErrorData);
|
||||
|
||||
// Clear component state
|
||||
useEffect(() => {
|
||||
if (disabled && value !== "") {
|
||||
handleOnNewValue({ value: "", file_path: "" }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled, handleOnNewValue]);
|
||||
|
||||
function checkFileType(fileName: string): boolean {
|
||||
if (fileTypes === undefined) return true;
|
||||
for (let index = 0; index < fileTypes.length; index++) {
|
||||
if (fileName.endsWith(fileTypes[index])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const { mutate, isPending } = usePostUploadFile();
|
||||
|
||||
const handleButtonClick = (): void => {
|
||||
createFileUpload({ multiple: false, accept: fileTypes?.join(",") }).then(
|
||||
(files) => {
|
||||
const file = files[0];
|
||||
if (file) {
|
||||
if (!validateFileSize(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkFileType(file.name)) {
|
||||
// Upload the file
|
||||
mutate(
|
||||
{ file, id: currentFlowId },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
|
||||
// sets the value that goes to the backend
|
||||
// Update the state and on with the name of the file
|
||||
// sets the value to the user
|
||||
handleOnNewValue({ value: file.name, file_path });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(CONSOLE_ERROR_MSG);
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
list: [error.response?.data?.detail],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Show an error if the file type is not allowed
|
||||
setErrorData({
|
||||
title: INVALID_FILE_ALERT,
|
||||
list: fileTypes,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={disabled ? "input-component-div" : "w-full"}>
|
||||
<div className="input-file-component gap-3">
|
||||
<span
|
||||
data-testid={id}
|
||||
onClick={handleButtonClick}
|
||||
className={
|
||||
editNode
|
||||
? "input-edit-node input-dialog text-muted-foreground"
|
||||
: disabled
|
||||
? "input-disable input-dialog primary-input"
|
||||
: "input-dialog primary-input text-muted-foreground"
|
||||
}
|
||||
>
|
||||
{value !== "" ? value : "No file"}
|
||||
</span>
|
||||
{!editNode && (
|
||||
<Button
|
||||
unstyled
|
||||
className="inline-flex items-center justify-center"
|
||||
onClick={handleButtonClick}
|
||||
loading={isPending}
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconComponent
|
||||
name="FileSearch2"
|
||||
className="icons-parameters-comp shrink-0"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
useDeleteGlobalVariables,
|
||||
useGetGlobalVariables,
|
||||
} from "@/controllers/API/queries/variables";
|
||||
import { useEffect } from "react";
|
||||
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import GlobalVariableModal from "../../../GlobalVariableModal/GlobalVariableModal";
|
||||
import ForwardedIconComponent from "../../../genericIconComponent";
|
||||
import InputComponent from "../../../inputComponent";
|
||||
import { CommandItem } from "../../../ui/command";
|
||||
import { InputGlobalComponentType, InputProps } from "../../types";
|
||||
|
||||
export default function InputGlobalComponent({
|
||||
disabled,
|
||||
handleOnNewValue,
|
||||
value,
|
||||
id,
|
||||
load_from_db,
|
||||
password,
|
||||
editNode = false,
|
||||
}: InputProps<string, InputGlobalComponentType>): JSX.Element {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const { data: globalVariables } = useGetGlobalVariables();
|
||||
const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables();
|
||||
|
||||
useEffect(() => {
|
||||
if (globalVariables)
|
||||
if (
|
||||
load_from_db &&
|
||||
!globalVariables.find((variable) => variable.name === value)
|
||||
) {
|
||||
handleOnNewValue(
|
||||
{ value: "", load_from_db: false },
|
||||
{ skipSnapshot: true },
|
||||
);
|
||||
}
|
||||
}, [globalVariables]);
|
||||
|
||||
async function handleDelete(key: string) {
|
||||
if (!globalVariables) return;
|
||||
const id = globalVariables.find((variable) => variable.name === key)?.id;
|
||||
if (id !== undefined) {
|
||||
mutateDeleteGlobalVariable(
|
||||
{ id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
if (value === key && load_from_db) {
|
||||
handleOnNewValue({ value: "", load_from_db: false });
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
setErrorData({
|
||||
title: "Error deleting variable",
|
||||
list: [cn("ID not found for variable: ", key)],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error deleting variable",
|
||||
list: [cn("ID not found for variable: ", key)],
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
<InputComponent
|
||||
id={id}
|
||||
editNode={editNode}
|
||||
disabled={disabled}
|
||||
password={password ?? false}
|
||||
value={value ?? ""}
|
||||
options={globalVariables?.map((variable) => variable.name) ?? []}
|
||||
optionsPlaceholder={"Global Variables"}
|
||||
optionsIcon="Globe"
|
||||
optionsButton={
|
||||
<GlobalVariableModal>
|
||||
<CommandItem value="doNotFilter-addNewVariable">
|
||||
<ForwardedIconComponent
|
||||
name="Plus"
|
||||
className={cn("mr-2 h-4 w-4 text-primary")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Add New Variable</span>
|
||||
</CommandItem>
|
||||
</GlobalVariableModal>
|
||||
}
|
||||
optionButton={(option) => (
|
||||
<DeleteConfirmationModal
|
||||
onConfirm={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleDelete(option);
|
||||
}}
|
||||
description={'variable "' + option + '"'}
|
||||
asChild
|
||||
>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="pr-1"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</DeleteConfirmationModal>
|
||||
)}
|
||||
selectedOption={
|
||||
load_from_db &&
|
||||
globalVariables &&
|
||||
globalVariables?.map((variable) => variable.name).includes(value ?? "")
|
||||
? value
|
||||
: ""
|
||||
}
|
||||
setSelectedOption={(value) => {
|
||||
handleOnNewValue({
|
||||
value: value,
|
||||
load_from_db: value !== "" ? true : false,
|
||||
});
|
||||
}}
|
||||
onChange={(value, skipSnapshot) => {
|
||||
handleOnNewValue(
|
||||
{ value: value, load_from_db: false },
|
||||
{ skipSnapshot },
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import _ from "lodash";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { Input } from "../../../ui/input";
|
||||
import { InputListComponentType, InputProps } from "../../types";
|
||||
|
||||
export default function InputListComponent({
|
||||
value = [""],
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
editNode = false,
|
||||
componentName,
|
||||
id,
|
||||
}: InputProps<string[], InputListComponentType>): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled && value.length > 0 && value[0] !== "") {
|
||||
handleOnNewValue({ value: [""] }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
// @TODO Recursive Character Text Splitter - the value might be in string format, whereas the InputListComponent specifically requires an array format. To ensure smooth operation and prevent potential errors, it's crucial that we handle the conversion from a string to an array with the string as its element.
|
||||
if (typeof value === "string") {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
if (!value?.length) value = [""];
|
||||
|
||||
const handleInputChange = (index, newValue) => {
|
||||
const newInputList = _.cloneDeep(value);
|
||||
newInputList[index] = newValue;
|
||||
handleOnNewValue({ value: newInputList });
|
||||
};
|
||||
|
||||
const addNewInput = (e) => {
|
||||
e.preventDefault();
|
||||
const newInputList = _.cloneDeep(value);
|
||||
newInputList.push("");
|
||||
handleOnNewValue({ value: newInputList });
|
||||
};
|
||||
|
||||
const removeInput = (index, e) => {
|
||||
e.preventDefault();
|
||||
const newInputList = _.cloneDeep(value);
|
||||
newInputList.splice(index, 1);
|
||||
handleOnNewValue({ value: newInputList });
|
||||
};
|
||||
|
||||
const getButtonClassName = () =>
|
||||
classNames(
|
||||
disabled
|
||||
? "cursor-not-allowed text-muted-foreground"
|
||||
: "text-primary hover:text-accent-foreground",
|
||||
);
|
||||
|
||||
const getTestId = (type, index) =>
|
||||
`input-list-${type}-btn${editNode ? "-edit" : ""}_${componentName}-${index}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
value.length > 1 && editNode ? "my-1" : "",
|
||||
"flex w-full flex-col gap-3",
|
||||
)}
|
||||
>
|
||||
{value.map((singleValue, index) => (
|
||||
<div key={index} className="flex w-full gap-3">
|
||||
<Input
|
||||
disabled={disabled}
|
||||
type="text"
|
||||
value={singleValue}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder="Type something..."
|
||||
onChange={(event) => handleInputChange(index, event.target.value)}
|
||||
data-testid={`${id}_${index}`}
|
||||
/>
|
||||
<Button
|
||||
unstyled
|
||||
className={getButtonClassName()}
|
||||
onClick={index === 0 ? addNewInput : (e) => removeInput(index, e)}
|
||||
data-testid={getTestId(index === 0 ? "plus" : "minus", index)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconComponent
|
||||
name={index === 0 ? "Plus" : "X"}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { cn } from "@/utils/utils";
|
||||
import {
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
} from "@chakra-ui/number-input";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
||||
import { InputProps, IntComponentType } from "../../types";
|
||||
|
||||
export default function IntComponent({
|
||||
value,
|
||||
handleOnNewValue,
|
||||
rangeSpec,
|
||||
disabled,
|
||||
editNode = false,
|
||||
id = "",
|
||||
}: InputProps<number, IntComponentType>): JSX.Element {
|
||||
const min = -Infinity;
|
||||
// Clear component state
|
||||
useEffect(() => {
|
||||
if (disabled && value !== 0) {
|
||||
handleOnNewValue({ value: 0 }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled, handleOnNewValue]);
|
||||
|
||||
const [cursor, setCursor] = useState<number | null>(null);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.setSelectionRange(cursor, cursor);
|
||||
}, [ref, cursor, value]);
|
||||
|
||||
const handleChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCursor(e.target.selectionStart);
|
||||
handleOnNewValue({ value: Number(e.target.value) });
|
||||
};
|
||||
|
||||
const getStepValue = () => {
|
||||
return (Number.isInteger(rangeSpec?.step) ? rangeSpec.step : 1) ?? 1;
|
||||
};
|
||||
|
||||
const getMinValue = () => {
|
||||
return rangeSpec?.min ?? min;
|
||||
};
|
||||
|
||||
const getMaxValue = () => {
|
||||
return rangeSpec?.max ?? undefined;
|
||||
};
|
||||
|
||||
const getInputClassName = () => {
|
||||
return cn(
|
||||
editNode ? "input-edit-node" : "",
|
||||
"nopan nodelete nodrag noflow primary-input",
|
||||
);
|
||||
};
|
||||
|
||||
const handleNumberChange = (newValue) => {
|
||||
handleOnNewValue({ value: Number(newValue) });
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const inputValue = Number(event.target.value);
|
||||
if (inputValue < getMinValue()) {
|
||||
event.target.value = getMinValue().toString();
|
||||
}
|
||||
};
|
||||
|
||||
const inputRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<NumberInput
|
||||
id={id}
|
||||
step={getStepValue()}
|
||||
min={getMinValue()}
|
||||
max={getMaxValue()}
|
||||
onChange={handleNumberChange}
|
||||
value={value ?? ""}
|
||||
>
|
||||
<NumberInputField
|
||||
className={getInputClassName()}
|
||||
onChange={handleChangeInput}
|
||||
onKeyDown={(event) => handleKeyDown(event, value, "")}
|
||||
onInput={handleInputChange}
|
||||
disabled={disabled}
|
||||
placeholder={editNode ? "Integer number" : "Type an integer number"}
|
||||
data-testid={id}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<NumberInputStepper paddingRight={10}>
|
||||
<NumberIncrementStepper fontSize={8} marginTop={6} />
|
||||
<NumberDecrementStepper fontSize={8} marginBottom={6} />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
} from "@/utils/reactflowUtils";
|
||||
import { cloneDeep } from "lodash";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Input } from "../../../ui/input";
|
||||
import { InputProps, KeyPairListComponentType } from "../../types";
|
||||
|
||||
export default function KeypairListComponent({
|
||||
value,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
editNode = false,
|
||||
isList = true,
|
||||
id,
|
||||
}: InputProps<
|
||||
object[] | object | string,
|
||||
KeyPairListComponentType
|
||||
>): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled && value.length > 0 && value[0] !== "") {
|
||||
handleOnNewValue({ value: [{ "": "" }] }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const [duplicateKey, setDuplicateKey] = useState(false);
|
||||
|
||||
const values =
|
||||
Object.keys(value || {})?.length === 0 || !value
|
||||
? [{ "": "" }]
|
||||
: convertObjToArray(value, "dict");
|
||||
|
||||
Array.isArray(value) ? value : [value];
|
||||
|
||||
const handleNewValue = (newValue: any) => {
|
||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||
setDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||
if (isList) {
|
||||
handleOnNewValue({ value: valueToNumbers });
|
||||
} else handleOnNewValue({ value: valueToNumbers[0] });
|
||||
};
|
||||
|
||||
const handleChangeKey = (event, idx) => {
|
||||
const oldKey = Object.keys(values[idx])[0];
|
||||
const updatedObj = { [event.target.value]: values[idx][oldKey] };
|
||||
|
||||
const newValue = cloneDeep(values);
|
||||
newValue[idx] = updatedObj;
|
||||
|
||||
handleNewValue(newValue);
|
||||
};
|
||||
|
||||
const handleChangeValue = (event, idx) => {
|
||||
const key = Object.keys(values[idx])[0];
|
||||
const updatedObj = { [key]: event.target.value };
|
||||
|
||||
const newValue = cloneDeep(values);
|
||||
newValue[idx] = updatedObj;
|
||||
|
||||
handleNewValue(newValue);
|
||||
};
|
||||
|
||||
const addNewKeyValuePair = () => {
|
||||
const newValues = cloneDeep(values);
|
||||
newValues.push({ "": "" });
|
||||
handleOnNewValue({ value: newValues });
|
||||
};
|
||||
|
||||
const removeKeyValuePair = (index) => {
|
||||
const newValues = cloneDeep(values);
|
||||
newValues.splice(index, 1);
|
||||
handleOnNewValue({ value: newValues });
|
||||
};
|
||||
|
||||
const getInputClassName = (isEditNode, isDuplicateKey) => {
|
||||
return `${isEditNode ? "input-edit-node" : ""} ${isDuplicateKey ? "input-invalid" : ""}`.trim();
|
||||
};
|
||||
|
||||
const getTestId = (prefix, index) =>
|
||||
`${editNode ? "editNode" : ""}${prefix}${index}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-full flex-col gap-3 ${values?.length > 1 && editNode ? "mx-2 my-1" : ""}`}
|
||||
>
|
||||
{values?.map((obj, index) =>
|
||||
Object.keys(obj).map((key, idx) => (
|
||||
<div key={idx} className="flex w-full gap-2">
|
||||
<Input
|
||||
data-testid={getTestId("keypair", index)}
|
||||
id={getTestId("keypair", index)}
|
||||
type="text"
|
||||
value={key.trim()}
|
||||
className={getInputClassName(editNode, duplicateKey)}
|
||||
placeholder="Type key..."
|
||||
onChange={(event) => handleChangeKey(event, index)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
data-testid={getTestId("keypair", index + 100)}
|
||||
id={getTestId("keypair", index + 100)}
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
value={obj[key]}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder="Type a value..."
|
||||
onChange={(event) => handleChangeValue(event, index)}
|
||||
/>
|
||||
|
||||
{isList &&
|
||||
(index === values.length - 1 ? (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={addNewKeyValuePair}
|
||||
id={getTestId("plusbtn", index)}
|
||||
data-testid={id}
|
||||
>
|
||||
<IconComponent
|
||||
name="Plus"
|
||||
className="h-4 w-4 hover:text-accent-foreground"
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => removeKeyValuePair(index)}
|
||||
data-testid={getTestId("minusbtn", index)}
|
||||
id={getTestId("minusbtn", index)}
|
||||
>
|
||||
<IconComponent
|
||||
name="X"
|
||||
className="h-4 w-4 hover:text-status-red"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { classNames } from "../../../../utils/utils";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { InputProps, LinkComponentType } from "../../types";
|
||||
|
||||
const DEFAULT_ICON = "ExternalLink";
|
||||
|
||||
export default function LinkComponent({
|
||||
value,
|
||||
disabled = false,
|
||||
id = "",
|
||||
text,
|
||||
icon,
|
||||
editNode = false,
|
||||
handleOnNewValue,
|
||||
}: InputProps<string, LinkComponentType>): JSX.Element {
|
||||
function handleOpenLink() {
|
||||
if (value) {
|
||||
const url = !/^https?:\/\//i.test(value) ? `https://${value}` : value;
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
}
|
||||
|
||||
const buttonClassName = classNames(
|
||||
"nopan w-full shrink-0",
|
||||
disabled ? "cursor-not-allowed text-ring" : "hover:text-accent-foreground",
|
||||
);
|
||||
|
||||
const ButtonContent = ({ icon, text }: { icon: string; text: string }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<IconComponent
|
||||
name={icon ?? DEFAULT_ICON}
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{text && <span>{text}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center gap-3">
|
||||
<Button
|
||||
data-testid={id}
|
||||
onClick={handleOpenLink}
|
||||
disabled={disabled || !value}
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className={buttonClassName}
|
||||
>
|
||||
<ButtonContent icon={icon ?? DEFAULT_ICON} text={text ?? ""} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
import { PopoverAnchor } from "@radix-ui/react-popover";
|
||||
import Fuse from "fuse.js";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import { default as ForwardedIconComponent } from "../../../genericIconComponent";
|
||||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "../../../ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverContentWithoutPortal,
|
||||
PopoverTrigger,
|
||||
} from "../../../ui/popover";
|
||||
import { InputProps, MultiselectComponentType } from "../../types";
|
||||
|
||||
export default function MultiselectComponent({
|
||||
disabled,
|
||||
value,
|
||||
options: defaultOptions,
|
||||
handleOnNewValue,
|
||||
combobox,
|
||||
editNode = false,
|
||||
id = "",
|
||||
}: InputProps<string[], MultiselectComponentType>): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const treatedValue = typeof value === "string" ? [value] : value;
|
||||
|
||||
const refButton = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const PopoverContentDropdown = editNode
|
||||
? PopoverContent
|
||||
: PopoverContentWithoutPortal;
|
||||
|
||||
const [customValues, setCustomValues] = useState<string[]>([]);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [filteredOptions, setFilteredOptions] = useState(defaultOptions);
|
||||
const [onlySelected, setOnlySelected] = useState(false);
|
||||
|
||||
const [options, setOptions] = useState<string[]>(defaultOptions);
|
||||
|
||||
const fuseOptions = new Fuse(options, { keys: ["name", "value"] });
|
||||
const fuseValues = new Fuse(treatedValue, { keys: ["name", "value"] });
|
||||
|
||||
const searchRoleByTerm = async (v: string) => {
|
||||
const fuse = onlySelected ? fuseValues : fuseOptions;
|
||||
const searchValues = fuse.search(v);
|
||||
let filtered: string[] = searchValues.map((search) => search.item);
|
||||
if (!filtered.includes(v) && combobox && v) filtered = [v, ...filtered];
|
||||
setFilteredOptions(
|
||||
v
|
||||
? filtered
|
||||
: onlySelected
|
||||
? options.filter((x) => treatedValue.includes(x))
|
||||
: options,
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled && treatedValue.length > 0 && treatedValue[0] !== "") {
|
||||
handleOnNewValue({ value: [] }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
searchRoleByTerm(searchValue);
|
||||
}, [onlySelected]);
|
||||
|
||||
useEffect(() => {
|
||||
searchRoleByTerm(searchValue);
|
||||
}, [options]);
|
||||
|
||||
useEffect(() => {
|
||||
setCustomValues(
|
||||
treatedValue.filter((v) => !defaultOptions.includes(v)) ?? [],
|
||||
);
|
||||
setOptions([
|
||||
...treatedValue.filter((v) => !defaultOptions.includes(v) && v),
|
||||
...defaultOptions,
|
||||
]);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setOnlySelected(false);
|
||||
setSearchValue("");
|
||||
searchRoleByTerm("");
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleOptionSelect = (currentValue) => {
|
||||
if (treatedValue.includes(currentValue)) {
|
||||
handleOnNewValue({
|
||||
value: treatedValue.filter((v) => v !== currentValue),
|
||||
});
|
||||
} else {
|
||||
handleOnNewValue({ value: [...treatedValue, currentValue] });
|
||||
}
|
||||
};
|
||||
|
||||
const renderDropdownTrigger = () => (
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="primary"
|
||||
size="xs"
|
||||
role="combobox"
|
||||
ref={refButton}
|
||||
aria-expanded={open}
|
||||
data-testid={id}
|
||||
className={cn(
|
||||
editNode
|
||||
? "dropdown-component-outline input-edit-node"
|
||||
: "dropdown-component-false-outline py-2",
|
||||
"w-full justify-between font-normal",
|
||||
)}
|
||||
>
|
||||
<span className="truncate" data-testid={`value-dropdown-${id}`}>
|
||||
{treatedValue.length > 0 &&
|
||||
options.find((option) => treatedValue.includes(option))
|
||||
? treatedValue.join(", ")
|
||||
: "Choose an option..."}
|
||||
</span>
|
||||
<ForwardedIconComponent
|
||||
name="ChevronsUpDown"
|
||||
className="ml-2 h-4 w-4 shrink-0 opacity-50"
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
);
|
||||
|
||||
const renderSearchInput = () => (
|
||||
<div className="flex items-center border-b px-3">
|
||||
<ForwardedIconComponent
|
||||
name="search"
|
||||
className="mr-2 h-4 w-4 shrink-0 opacity-50"
|
||||
/>
|
||||
<input
|
||||
onChange={(event) => {
|
||||
setSearchValue(event.target.value);
|
||||
}}
|
||||
placeholder="Search options..."
|
||||
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<Button
|
||||
unstyled
|
||||
className="ml-2"
|
||||
onClick={() => setOnlySelected((old) => !old)}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
className="h-4 w-4"
|
||||
name={onlySelected ? "CheckCheck" : "Check"}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderOptionsList = () => (
|
||||
<CommandList className="overflow-y-scroll">
|
||||
<CommandEmpty>No values found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{filteredOptions.map((option, index) => (
|
||||
<ShadTooltip key={index} delayDuration={700} content={option}>
|
||||
<div>
|
||||
<CommandItem
|
||||
value={option}
|
||||
onSelect={handleOptionSelect}
|
||||
className="items-center overflow-hidden truncate"
|
||||
data-testid={`${option}-${id ?? ""}-option`}
|
||||
>
|
||||
{(customValues.includes(option) || searchValue === option) && (
|
||||
<span className="text-muted-foreground">Text: </span>
|
||||
)}
|
||||
<span className="truncate">{option}</span>
|
||||
<ForwardedIconComponent
|
||||
name="Check"
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4 shrink-0 text-primary",
|
||||
treatedValue.includes(option) ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
);
|
||||
|
||||
if (Object.keys(options).length === 0 && !combobox) {
|
||||
return (
|
||||
<div>
|
||||
<span className="text-sm italic">
|
||||
No parameters are available for display.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
{renderDropdownTrigger()}
|
||||
<PopoverContentDropdown
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
side="bottom"
|
||||
avoidCollisions={false}
|
||||
className="noflow nowheel nopan nodelete nodrag p-0"
|
||||
style={{ minWidth: refButton?.current?.clientWidth ?? "200px" }}
|
||||
>
|
||||
<Command>
|
||||
{renderSearchInput()}
|
||||
{renderOptionsList()}
|
||||
</Command>
|
||||
</PopoverContentDropdown>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import PromptModal from "@/modals/promptModal";
|
||||
import { useEffect } from "react";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { InputProps, PromptAreaComponentType } from "../../types";
|
||||
|
||||
export default function PromptAreaComponent({
|
||||
field_name,
|
||||
nodeClass,
|
||||
handleOnNewValue,
|
||||
handleNodeClass,
|
||||
value,
|
||||
disabled,
|
||||
editNode = false,
|
||||
id = "",
|
||||
readonly = false,
|
||||
}: InputProps<string, PromptAreaComponentType>): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled && value !== "") {
|
||||
handleOnNewValue({ value: "" }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const renderPromptText = () => (
|
||||
<span
|
||||
id={id}
|
||||
data-testid={id}
|
||||
className={cn(
|
||||
editNode
|
||||
? "input-edit-node input-dialog"
|
||||
: "primary-input text-muted-foreground",
|
||||
disabled && !editNode && "input-disable text-ring",
|
||||
)}
|
||||
>
|
||||
{value !== "" ? value : "Type your prompt here..."}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderExternalLinkIcon = () => {
|
||||
if (editNode) return null;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={cn(
|
||||
"icons-parameters-comp shrink-0",
|
||||
disabled ? "text-ring" : "hover:text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("w-full", disabled && "pointer-events-none")}>
|
||||
<PromptModal
|
||||
id={id}
|
||||
field_name={field_name}
|
||||
readonly={readonly}
|
||||
value={value}
|
||||
setValue={() => handleOnNewValue({ value: "" })}
|
||||
nodeClass={nodeClass}
|
||||
setNodeClass={handleNodeClass}
|
||||
>
|
||||
<Button unstyled className="w-full">
|
||||
<div className="flex w-full items-center gap-3">
|
||||
{renderPromptText()}
|
||||
{renderExternalLinkIcon()}
|
||||
</div>
|
||||
</Button>
|
||||
</PromptModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { RefreshButton } from "@/components/ui/refreshButton";
|
||||
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
||||
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import { APIClassType, InputFieldType } from "@/types/api";
|
||||
import { InputProps } from "../../types";
|
||||
|
||||
export function RefreshParameterComponent({
|
||||
children,
|
||||
templateData,
|
||||
disabled,
|
||||
nodeClass,
|
||||
editNode,
|
||||
handleNodeClass,
|
||||
nodeId,
|
||||
name,
|
||||
}: {
|
||||
children: React.ReactElement<InputProps>;
|
||||
templateData: Partial<InputFieldType>;
|
||||
disabled: boolean;
|
||||
nodeClass: APIClassType;
|
||||
editNode: boolean;
|
||||
handleNodeClass: (value: any, code?: string, type?: string) => void;
|
||||
nodeId: string;
|
||||
name: string;
|
||||
}) {
|
||||
const postTemplateValue = usePostTemplateValue({
|
||||
parameterId: name,
|
||||
nodeId: nodeId,
|
||||
node: nodeClass,
|
||||
});
|
||||
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const handleRefreshButtonPress = () =>
|
||||
mutateTemplate(
|
||||
templateData.value,
|
||||
nodeClass,
|
||||
handleNodeClass,
|
||||
postTemplateValue,
|
||||
setErrorData,
|
||||
);
|
||||
return (
|
||||
(children || templateData.refresh_button) && (
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
{children}
|
||||
{templateData.refresh_button && (
|
||||
<div className="shrink-0 flex-col">
|
||||
<RefreshButton
|
||||
isLoading={postTemplateValue.isPending}
|
||||
disabled={disabled}
|
||||
editNode={editNode}
|
||||
button_text={templateData.refresh_button_text}
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { InputProps, StrRenderComponentType } from "../../types";
|
||||
import DropdownComponent from "../dropdownComponent";
|
||||
import InputGlobalComponent from "../inputGlobalComponent";
|
||||
import TextAreaComponent from "../textAreaComponent";
|
||||
|
||||
export function StrRenderComponent({
|
||||
templateData,
|
||||
name,
|
||||
...baseInputProps
|
||||
}: InputProps<string, StrRenderComponentType>) {
|
||||
const { handleOnNewValue, id, disabled, editNode, value } = baseInputProps;
|
||||
|
||||
if (!templateData.options) {
|
||||
return templateData.multiline ? (
|
||||
<TextAreaComponent
|
||||
{...baseInputProps}
|
||||
password={templateData.password}
|
||||
updateVisibility={() => {
|
||||
if (templateData.password !== undefined) {
|
||||
handleOnNewValue(
|
||||
{ password: !templateData.password },
|
||||
{ skipSnapshot: true },
|
||||
);
|
||||
}
|
||||
}}
|
||||
id={`textarea_${id}`}
|
||||
/>
|
||||
) : (
|
||||
<InputGlobalComponent
|
||||
{...baseInputProps}
|
||||
password={templateData.password}
|
||||
load_from_db={templateData.load_from_db}
|
||||
id={"input-" + name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!!templateData.options) {
|
||||
return (
|
||||
<DropdownComponent
|
||||
{...baseInputProps}
|
||||
options={templateData.options}
|
||||
combobox={templateData.combobox}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import ComponentTextModal from "@/modals/textAreaModal";
|
||||
import { classNames } from "@/utils/utils";
|
||||
import { useEffect } from "react";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { Textarea } from "../../../ui/textarea";
|
||||
import { InputProps, TextAreaComponentType } from "../../types";
|
||||
|
||||
export default function TextAreaComponent({
|
||||
value,
|
||||
disabled,
|
||||
handleOnNewValue,
|
||||
editNode = false,
|
||||
id = "",
|
||||
password,
|
||||
updateVisibility,
|
||||
}: InputProps<string, TextAreaComponentType>): JSX.Element {
|
||||
// Clear text area
|
||||
useEffect(() => {
|
||||
if (disabled && value !== "") {
|
||||
handleOnNewValue({ value: "" }, { skipSnapshot: true });
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const renderTextarea = () => (
|
||||
<Textarea
|
||||
id={id}
|
||||
data-testid={id}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
"w-full resize-none",
|
||||
password !== undefined && password && value !== ""
|
||||
? "text-clip password"
|
||||
: "",
|
||||
editNode ? "input-edit-node" : "",
|
||||
password !== undefined ? "pr-8" : "",
|
||||
)}
|
||||
rows={1}
|
||||
placeholder="Type something..."
|
||||
onChange={(event) => handleOnNewValue({ value: event.target.value })}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderExternalLinkButton = () => (
|
||||
<ComponentTextModal
|
||||
changeVisibility={updateVisibility}
|
||||
value={value}
|
||||
setValue={(value) => handleOnNewValue({ value: value })}
|
||||
disabled={disabled}
|
||||
password={password}
|
||||
>
|
||||
<div className={classNames("flex items-center")}>
|
||||
<Button unstyled disabled={disabled}>
|
||||
<IconComponent
|
||||
strokeWidth={1.5}
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={classNames(
|
||||
"icons-parameters-comp shrink-0",
|
||||
disabled
|
||||
? "cursor-not-allowed text-ring"
|
||||
: "hover:text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentTextModal>
|
||||
);
|
||||
|
||||
const renderPasswordToggle = () => {
|
||||
if (password === undefined) return null;
|
||||
|
||||
return (
|
||||
<Button
|
||||
unstyled
|
||||
tabIndex={-1}
|
||||
className={classNames(
|
||||
"side-bar-button-size absolute right-3 top-1/2 mb-px -translate-y-1/2 text-muted-foreground hover:text-current",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
if (updateVisibility) updateVisibility();
|
||||
}}
|
||||
>
|
||||
{password ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames("flex w-full items-center gap-3")}>
|
||||
<div className="relative w-full" data-testid={`div-${id}`}>
|
||||
{renderTextarea()}
|
||||
{renderPasswordToggle()}
|
||||
</div>
|
||||
{renderExternalLinkButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { Switch } from "../../../ui/switch";
|
||||
import { InputProps, ToggleComponentType } from "../../types";
|
||||
|
||||
export default function ToggleShadComponent({
|
||||
value,
|
||||
editNode,
|
||||
handleOnNewValue,
|
||||
disabled,
|
||||
size,
|
||||
showToogle,
|
||||
id,
|
||||
}: InputProps<boolean, ToggleComponentType>): JSX.Element {
|
||||
let scaleX, scaleY;
|
||||
switch (size) {
|
||||
case "small":
|
||||
scaleX = 0.6;
|
||||
scaleY = 0.6;
|
||||
break;
|
||||
case "medium":
|
||||
scaleX = 0.8;
|
||||
scaleY = 0.8;
|
||||
break;
|
||||
case "large":
|
||||
scaleX = 1;
|
||||
scaleY = 1;
|
||||
break;
|
||||
default:
|
||||
if (editNode) {
|
||||
scaleX = 0.6;
|
||||
scaleY = 0.6;
|
||||
} else {
|
||||
scaleX = 1;
|
||||
scaleY = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch
|
||||
id={id}
|
||||
data-testid={id}
|
||||
style={{
|
||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
}}
|
||||
disabled={disabled}
|
||||
className=""
|
||||
checked={value}
|
||||
onCheckedChange={(isEnabled: boolean) => {
|
||||
const data = showToogle
|
||||
? { advanced: !isEnabled }
|
||||
: { value: isEnabled };
|
||||
handleOnNewValue(data);
|
||||
}}
|
||||
></Switch>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue