Updated global variables to ShadCN and updated logic to show variables as different things

This commit is contained in:
Lucas Oliveira 2024-03-20 18:05:16 +01:00
commit d003be4fe2
7 changed files with 202 additions and 169 deletions

View file

@ -42,6 +42,7 @@
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.1", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^1.0.0",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"esbuild": "^0.17.19", "esbuild": "^0.17.19",
"framer-motion": "^11.0.6", "framer-motion": "^11.0.6",
@ -4797,6 +4798,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cmdk": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz",
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
"dependencies": {
"@radix-ui/react-dialog": "1.0.5",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/code-block-writer": { "node_modules/code-block-writer": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",

View file

@ -37,6 +37,7 @@
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.1", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^1.0.0",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"esbuild": "^0.17.19", "esbuild": "^0.17.19",
"framer-motion": "^11.0.6", "framer-motion": "^11.0.6",

View file

@ -16,6 +16,7 @@ import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent"; import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import { CommandItem } from "../../../../components/ui/command";
import { RefreshButton } from "../../../../components/ui/refreshButton"; import { RefreshButton } from "../../../../components/ui/refreshButton";
import { import {
INPUT_HANDLER_HOVER, INPUT_HANDLER_HOVER,
@ -23,6 +24,7 @@ import {
OUTPUT_HANDLER_HOVER, OUTPUT_HANDLER_HOVER,
TOOLTIP_EMPTY, TOOLTIP_EMPTY,
} from "../../../../constants/constants"; } from "../../../../constants/constants";
import AddNewVariableButton from "../../../../pages/globalVariablesPage/components/addNewVariableButton";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -46,7 +48,7 @@ import {
nodeIconsLucide, nodeIconsLucide,
nodeNames, nodeNames,
} from "../../../../utils/styleUtils"; } from "../../../../utils/styleUtils";
import { classNames, groupByFamily } from "../../../../utils/utils"; import { classNames, cn, groupByFamily } from "../../../../utils/utils";
export default function ParameterComponent({ export default function ParameterComponent({
left, left,
@ -386,7 +388,7 @@ export default function ParameterComponent({
<> <>
<div <div
className={ className={
"w-full truncate text-sm flex items-center" + "flex w-full items-center truncate text-sm" +
(left ? "" : " text-end") (left ? "" : " text-end")
} }
> >
@ -546,33 +548,47 @@ export default function ParameterComponent({
password={data.node?.template[name].password ?? false} password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""} value={data.node?.template[name].value ?? ""}
options={globalVariablesEntries} options={globalVariablesEntries}
onChange={(value) => { optionsPlaceholder={"Global Variables"}
handleOnNewValue(value); optionsButton={
if (globalVariablesEntries.includes(value)) { <AddNewVariableButton>
setNoticeData({ <CommandItem value={"new"}>
title: `the value inserted in ${data.node?.display_name} is a global variable, \n <IconComponent
the real value will be update on run`, name="Plus"
}); className={cn("mr-2 h-4 w-4 text-primary")}
setNode(data.id, (oldNode) => { aria-hidden="true"
let newNode = cloneDeep(oldNode); />
newNode.data = { Add New Variable
...newNode.data, </CommandItem>
}; </AddNewVariableButton>
newNode.data.node.template[name].load_from_db = true; }
return newNode; selectedOption={
}); data?.node?.template[name].load_from_db ?? false
} else { ? data?.node?.template[name].value
setNode(data.id, (oldNode) => { : ""
let newNode = cloneDeep(oldNode); }
newNode.data = { setSelectedOption={(value) => {
...newNode.data, handleOnNewValue(value);
}; setNode(data.id, (oldNode) => {
newNode.data.node.template[name].load_from_db = false; let newNode = cloneDeep(oldNode);
return newNode; newNode.data = {
}); ...newNode.data,
} };
//mark as global variable newNode.data.node.template[name].load_from_db =
}} value === "" ? false : true;
return newNode;
});
}}
onChange={(value) => {
handleOnNewValue(value);
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name].load_from_db = false;
return newNode;
});
}}
/> />
</div> </div>
{data.node?.template[name].refresh_button && ( {data.node?.template[name].refresh_button && (

View file

@ -1,13 +1,20 @@
import { Listbox, Transition } from "@headlessui/react";
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { Fragment, useEffect, useRef, useState } from "react"; import { PopoverAnchor } from "@radix-ui/react-popover";
import AddNewVariableButton from "../../pages/globalVariablesPage/components/addNewVariableButton"; import { useEffect, useRef, useState } from "react";
import { InputComponentType } from "../../types/components"; import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils"; import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames, cn } from "../../utils/utils"; import { classNames, cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../ui/command";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Separator } from "../ui/separator"; import { Popover, PopoverContentWithoutPortal } from "../ui/popover";
export default function InputComponent({ export default function InputComponent({
autoFocus = false, autoFocus = false,
@ -23,7 +30,11 @@ export default function InputComponent({
className, className,
id = "", id = "",
blurOnEnter = false, blurOnEnter = false,
selectedOption,
setSelectedOption,
options = [], options = [],
optionsPlaceholder = "Search options...",
optionsButton,
}: InputComponentType): JSX.Element { }: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false); const [pwdVisible, setPwdVisible] = useState(false);
const refInput = useRef<HTMLInputElement>(null); const refInput = useRef<HTMLInputElement>(null);
@ -36,10 +47,6 @@ export default function InputComponent({
} }
}, [disabled]); }, [disabled]);
const filteredOptions = options.filter((option) =>
option.toLowerCase().includes(value.toLowerCase())
);
function onInputLostFocus(event): void { function onInputLostFocus(event): void {
if (onBlur) onBlur(event); if (onBlur) onBlur(event);
} }
@ -81,144 +88,118 @@ export default function InputComponent({
</Form.Control> </Form.Control>
) : ( ) : (
<> <>
<Input <Popover open={showOptions} onOpenChange={setShowOptions}>
id={id} <PopoverAnchor>
ref={refInput} <Input
type="text" id={id}
onBlur={onInputLostFocus} ref={refInput}
value={value} type="text"
autoFocus={autoFocus} onBlur={onInputLostFocus}
disabled={disabled} value={value}
required={required} autoFocus={autoFocus}
className={classNames( disabled={disabled || selectedOption !== ""}
password && !pwdVisible && value !== "" onClick={() => {
? " text-clip password " selectedOption !== "" && setShowOptions(true);
: "", }}
editNode ? " input-edit-node " : "", required={required}
password && editNode ? "pr-8" : "", className={classNames(
password && !editNode ? "pr-10" : "", password && !pwdVisible && value !== ""
className! ? " text-clip password "
)} : "",
placeholder={password && editNode ? "Key" : placeholder} editNode ? " input-edit-node " : "",
onChange={(e) => { password && editNode ? "pr-8" : "",
onChange(e.target.value); password && !editNode ? "pr-10" : "",
}} className!
onKeyDown={(e) => { )}
handleKeyDown(e, value, ""); placeholder={password && editNode ? "Key" : placeholder}
if (blurOnEnter && e.key === "Enter") refInput.current?.blur(); onChange={(e) => {
}} onChange(e.target.value);
data-testid={editNode ? id + "-edit" : id} }}
/> onKeyDown={(e) => {
<Listbox handleKeyDown(e, value, "");
onChange={(val) => { if (blurOnEnter && e.key === "Enter")
onChange(val); refInput.current?.blur();
}} }}
> data-testid={editNode ? id + "-edit" : id}
<> />
<div className={"relative mt-1 "}> </PopoverAnchor>
<Transition <PopoverContentWithoutPortal
show={showOptions} className="nocopy nopan nodelete nodrag noundo p-0"
as={Fragment} style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
leave="transition ease-in duration-100" side="bottom"
leaveFrom="opacity-100" align="center"
leaveTo="opacity-0" >
> <Command>
<Listbox.Options <CommandInput placeholder={optionsPlaceholder} />
className={classNames( <CommandList>
editNode <CommandEmpty>No results found.</CommandEmpty>
? "dropdown-component-true-options nowheel custom-scroll" <CommandGroup defaultChecked={false}>
: "dropdown-component-false-options nowheel custom-scroll", {options.map((option, id) => (
false ? "mb-2 w-[250px]" : "absolute w-full" <CommandItem
)}
>
<div className="flex items-center justify-between px-4 pb-3 pt-2 font-semibold">
<div className="flex items-center gap-2">
<IconComponent name="Globe" className="h-4 w-4" />
Global Variables
</div>
<div>
<AddNewVariableButton>
<button className="text-muted-foreground hover:text-accent-foreground">
<IconComponent name="Plus" className="h-5 w-5" />
</button>
</AddNewVariableButton>
</div>
</div>
<Separator />
{filteredOptions.map((option, id) => (
<Listbox.Option
key={id} key={id}
className={({ active }) =>
classNames(
active ? " bg-accent" : "",
editNode
? "dropdown-component-false-option"
: "dropdown-component-true-option",
" hover:bg-accent"
)
}
value={option} value={option}
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption
? ""
: currentValue
);
setShowOptions(false);
}}
> >
{({ selected, active }) => ( <IconComponent
<> name="Check"
<span className={cn(
className={classNames( "mr-2 h-4 w-4 text-primary",
selected ? "font-semibold" : "font-normal", selectedOption === option
"block truncate " ? "opacity-100"
)} : "opacity-0"
data-testid={`${option}-${id ?? ""}-option`} )}
> aria-hidden="true"
{option} />
</span> {option}
</CommandItem>
{selected ? (
<span
className={classNames(
"dropdown-component-choosal"
)}
>
<IconComponent
name="Check"
className={
"dropdown-component-check-icon text-foreground"
}
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))} ))}
</Listbox.Options> {optionsButton && optionsButton}
</Transition> </CommandGroup>
</div> </CommandList>
</> </Command>
</Listbox> </PopoverContentWithoutPortal>
</Popover>
<div
className={cn(
"pointer-events-auto absolute inset-y-0 h-full w-full",
selectedOption !== "" ? "" : "hidden"
)}
onClick={
selectedOption !== ""
? (e) => {
setShowOptions((old) => !old);
e.preventDefault();
e.stopPropagation();
}
: () => {}
}
></div>
</> </>
)} )}
{options.length > 0 && ( <span
<span className={cn(
className={cn( password ? "right-8" : "right-0",
password ? "right-8" : "right-0", "absolute inset-y-0 flex items-center pr-2"
"absolute inset-y-0 flex items-center pr-2" )}
)} >
<button
onClick={() => {
setShowOptions(!showOptions);
}}
className="text-muted-foreground hover:text-accent-foreground"
> >
<button <IconComponent name="Globe" className="h-4 w-4" aria-hidden="true" />
onClick={() => { </button>
setShowOptions(!showOptions); </span>
}}
className="text-muted-foreground hover:text-accent-foreground"
>
<IconComponent
name="Globe"
className="h-4 w-4"
aria-hidden="true"
/>
</button>
</span>
)}
{password && ( {password && (
<button <button

View file

@ -117,7 +117,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
className className
)} )}
{...props} {...props}

View file

@ -27,4 +27,21 @@ const PopoverContent = React.forwardRef<
)); ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName; PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverTrigger }; const PopoverContentWithoutPortal = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
PopoverContentWithoutPortal.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverContentWithoutPortal, PopoverTrigger };

View file

@ -20,7 +20,11 @@ export type InputComponentType = {
className?: string; className?: string;
id?: string; id?: string;
blurOnEnter?: boolean; blurOnEnter?: boolean;
optionsPlaceholder?: string;
options?: string[]; options?: string[];
optionsButton?: ReactElement;
selectedOption?: string;
setSelectedOption?: (value: string) => void;
}; };
export type ToggleComponentType = { export type ToggleComponentType = {
enabled: boolean; enabled: boolean;