diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index b55c44b46..374236168 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -32,6 +32,7 @@ "react-router-dom": "^6.8.1", "react-syntax-highlighter": "^15.5.0", "react-tabs": "^6.0.0", + "react-tooltip": "^5.13.1", "reactflow": "^11.5.5", "rehype-mathjax": "^4.0.2", "remark-gfm": "^3.0.1", @@ -897,6 +898,19 @@ "node": ">=12" } }, + "node_modules/@floating-ui/core": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", + "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + }, + "node_modules/@floating-ui/dom": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.9.tgz", + "integrity": "sha512-sosQxsqgxMNkV3C+3UqTS6LxP7isRLwX8WMepp843Rb3/b0Wz8+MdUkxJksByip3C2WwLugLHN1b4ibn//zKwQ==", + "dependencies": { + "@floating-ui/core": "^1.2.6" + } + }, "node_modules/@headlessui/react": { "version": "1.7.10", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.10.tgz", @@ -2938,6 +2952,11 @@ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.4.tgz", "integrity": "sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==" }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -6265,6 +6284,19 @@ "react": "^18.0.0" } }, + "node_modules/react-tooltip": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.13.1.tgz", + "integrity": "sha512-9NstDFdjyy6cIH9zjeT70zXTHlW/TIGCOWQmhkAyqLFeQioLg1FXvb9ec7AxSpn0zyFUkFSLdFYxZRuewti3Aw==", + "dependencies": { + "@floating-ui/dom": "^1.0.0", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 0b5c6a85b..bd846ce19 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -27,6 +27,7 @@ "react-router-dom": "^6.8.1", "react-syntax-highlighter": "^15.5.0", "react-tabs": "^6.0.0", + "react-tooltip": "^5.13.1", "reactflow": "^11.5.5", "rehype-mathjax": "^4.0.2", "remark-gfm": "^3.0.1", diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 5668b5430..653248763 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -144,6 +144,7 @@ export default function ParameterComponent({ ) : left === true && type === "float" ? ( { data.node.template[name].value = t; @@ -184,6 +185,7 @@ export default function ParameterComponent({ ) : left === true && type === "int" ? ( { data.node.template[name].value = t; diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 1493402e1..83806455b 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -28,6 +28,7 @@ import NodeModal from "../../modals/NodeModal"; import { useCallback } from "react"; import { TabsContext } from "../../contexts/tabsContext"; import { debounce } from "../../utils"; +import TooltipReact from "../../components/ReactTooltipComponent"; import Tooltip from "../../components/TooltipComponent"; export default function GenericNode({ data, @@ -112,50 +113,15 @@ export default function GenericNode({ color: nodeColors[types[data.type]] ?? nodeColors.unknown, }} /> - -
{data.type}
-
-
- - {validationStatus.params.split("\n").map((line, index) => ( -
{line}
- ))} -
- ) - } +
+ -
- - - -
- +
{data.type}
+
@@ -183,17 +149,61 @@ export default function GenericNode({ ) ? "" : "hidden", - "h-6 w-6 hover:animate-spin dark:text-gray-300" + "w-5 h-5 dark:text-gray-300" )} > + + +
+ + {validationStatus.params.split("\n").map((line, index) => ( +
{line}
+ ))} +
+ ) + } + > +
+ + + +
+ +
diff --git a/src/frontend/src/components/ReactTooltipComponent/index.tsx b/src/frontend/src/components/ReactTooltipComponent/index.tsx new file mode 100644 index 000000000..aa736c212 --- /dev/null +++ b/src/frontend/src/components/ReactTooltipComponent/index.tsx @@ -0,0 +1,54 @@ +"use client"; +import type { FC } from "react"; +import React from "react"; +import { Tooltip as ReactTooltip } from "react-tooltip"; +import "react-tooltip/dist/react-tooltip.css"; +import { classNames } from "../../utils"; + +type TooltipProps = { + selector: string; + content?: string; + disabled?: boolean; + htmlContent?: React.ReactNode; + className?: string; // This should use !impornant to override the default styles eg: '!bg-white' + position?: "top" | "right" | "bottom" | "left"; + clickable?: boolean; + children: React.ReactNode; + delayShow?: number; +}; + +const TooltipReact: FC = ({ + selector, + content, + disabled, + position = "top", + children, + htmlContent, + className, + clickable, + delayShow, +}) => { + return ( +
+ {React.cloneElement(children as React.ReactElement, { + "data-tooltip-id": selector, + })} + + {htmlContent && htmlContent} + +
+ ); +}; + +export default TooltipReact; diff --git a/src/frontend/src/components/floatComponent/index.tsx b/src/frontend/src/components/floatComponent/index.tsx index 4c4d0ad9a..1fe3e3f76 100644 --- a/src/frontend/src/components/floatComponent/index.tsx +++ b/src/frontend/src/components/floatComponent/index.tsx @@ -5,9 +5,12 @@ import { TabsContext } from "../../contexts/tabsContext"; export default function FloatComponent({ value, onChange, + disableCopyPaste = false, disabled, }: FloatComponentType) { const [myValue, setMyValue] = useState(value ?? ""); + const { setDisableCopyPaste } = useContext(TabsContext); + useEffect(() => { if (disabled) { setMyValue(""); @@ -15,8 +18,18 @@ export default function FloatComponent({ } }, [disabled, onChange]); return ( -
+
{ + if (disableCopyPaste) setDisableCopyPaste(true); + }} + onBlur={() => { + if (disableCopyPaste) setDisableCopyPaste(false); + }} type="number" value={myValue} className={ diff --git a/src/frontend/src/components/intComponent/index.tsx b/src/frontend/src/components/intComponent/index.tsx index f8887e895..5ca68c5ce 100644 --- a/src/frontend/src/components/intComponent/index.tsx +++ b/src/frontend/src/components/intComponent/index.tsx @@ -5,9 +5,12 @@ import { TabsContext } from "../../contexts/tabsContext"; export default function IntComponent({ value, onChange, + disableCopyPaste = false, disabled, }: FloatComponentType) { const [myValue, setMyValue] = useState(value ?? ""); + const { setDisableCopyPaste } = useContext(TabsContext); + useEffect(() => { if (disabled) { setMyValue(""); @@ -16,18 +19,32 @@ export default function IntComponent({ }, [disabled, onChange]); return (
{ + if (disableCopyPaste) setDisableCopyPaste(true); + }} + onBlur={() => { + if (disableCopyPaste) setDisableCopyPaste(false); + }} onKeyDown={(event) => { + console.log(event); if ( event.key !== "Backspace" && event.key !== "Enter" && event.key !== "Delete" && event.key !== "ArrowLeft" && event.key !== "ArrowRight" && + event.key !== "Control" && + event.key !== "Meta" && + event.key !== "Shift" && + event.key !== "c" && + event.key !== "v" && + event.key !== "a" && !/^[-]?\d*$/.test(event.key) ) { event.preventDefault(); diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 18927a480..a6f9e49cd 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -96,7 +96,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { cookieObject.flows.forEach((flow) => { flow.data.edges.forEach((edge) => { edge.className = ""; - edge.style = { stroke: "#222222" }; + edge.style = { stroke: "#555555" }; }); flow.data.nodes.forEach((node) => { if (Object.keys(templates[node.data.type]["template"]).length > 0) { @@ -285,7 +285,12 @@ export function TabsProvider({ children }: { children: ReactNode }) { sourceHandle, targetHandle, id, - className: "animate-pulse", + style: { stroke: "inherit" }, + className: + targetHandle.split("|")[0] === "Text" + ? "stroke-gray-800 dark:stroke-gray-300" + : "stroke-gray-900 dark:stroke-gray-200", + animated: targetHandle.split("|")[0] === "Text", selected: false, }, edges.map((e) => ({ ...e, selected: false })) @@ -301,8 +306,12 @@ export function TabsProvider({ children }: { children: ReactNode }) { const description = flow?.description ? flow.description : ""; if (data) { data.edges.forEach((edge) => { - edge.className = ""; - edge.style = { stroke: "#222222" }; + edge.style = { stroke: "inherit" }; + edge.className = + edge.targetHandle.split("|")[0] === "Text" + ? "stroke-gray-800 dark:stroke-gray-300" + : "stroke-gray-900 dark:stroke-gray-200"; + edge.animated = edge.targetHandle.split("|")[0] === "Text"; }); data.nodes.forEach((node) => { if (Object.keys(templates[node.data.type]["template"]).length > 0) { diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index 5e0f3e051..2db84adbc 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -151,13 +151,12 @@ export default function ChatMessage({ ) : (
- - {message} - + "), + }} + >
)} diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index 6bcedc60c..8aff12586 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -84,7 +84,7 @@ export default function ExportModal() { as="h3" className="text-lg font-medium dark:text-white leading-10 text-gray-900" > - Export as + Export
diff --git a/src/frontend/src/modals/importModal/index.tsx b/src/frontend/src/modals/importModal/index.tsx index aa25e058b..d0808c541 100644 --- a/src/frontend/src/modals/importModal/index.tsx +++ b/src/frontend/src/modals/importModal/index.tsx @@ -124,7 +124,7 @@ export default function ImportModal() { as="h3" className="text-lg font-medium dark:text-white leading-10 text-gray-900" > - {showExamples ? "Select an example" : "Import from"} + {showExamples ? "Select an example" : "Import"}
diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 7d7ebfb7c..b88e13586 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -4,7 +4,7 @@ import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; import { useContext, useEffect, useState } from "react"; import { typesContext } from "../../../../contexts/typesContext"; import { APIClassType, APIObjectType } from "../../../../types/api"; -import Tooltip from "../../../../components/TooltipComponent"; +import TooltipReact from "../../../../components/ReactTooltipComponent"; export default function ExtraSidebar() { const { data } = useContext(typesContext); @@ -15,7 +15,9 @@ export default function ExtraSidebar() { ) { //start drag event var crt = event.currentTarget.cloneNode(true); - crt.style.position = "absolute"; crt.style.top = "-500px"; crt.style.right = "-500px"; + crt.style.position = "absolute"; + crt.style.top = "-500px"; + crt.style.right = "-500px"; crt.classList.add("cursor-grabbing"); document.body.appendChild(crt); event.dataTransfer.setDragImage(crt, 0, 0); @@ -38,8 +40,14 @@ export default function ExtraSidebar() { {Object.keys(data[d]) .sort() .map((t: string, k) => ( - 21 ? t : ""} placement="right"> -
+ +
{ document.body.removeChild( - document.getElementsByClassName("cursor-grabbing")[0] + document.getElementsByClassName( + "cursor-grabbing" + )[0] ); }} > @@ -66,7 +76,7 @@ export default function ExtraSidebar() {
-
+ ))} {Object.keys(data[d]).length === 0 && (
Coming soon
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx index d8112410d..caaddfe0d 100644 --- a/src/frontend/src/pages/FlowPage/index.tsx +++ b/src/frontend/src/pages/FlowPage/index.tsx @@ -151,10 +151,11 @@ export default function FlowPage({ flow }: { flow: FlowType }) { addEdge( { ...params, - style: + style: { stroke: "inherit" }, + className: params.targetHandle.split("|")[0] === "Text" - ? { stroke: "#333333", strokeWidth: 2 } - : { stroke: "#222222" }, + ? "stroke-gray-800 dark:stroke-gray-300" + : "stroke-gray-900 dark:stroke-gray-200", animated: params.targetHandle.split("|")[0] === "Text", }, eds @@ -323,6 +324,7 @@ export default function FlowPage({ flow }: { flow: FlowType }) { onNodesChange={onNodesChange} onEdgesChange={onEdgesChangeMod} onConnect={onConnect} + disableKeyboardA11y={true} onLoad={setReactFlowInstance} onInit={setReactFlowInstance} nodeTypes={nodeTypes} @@ -339,6 +341,8 @@ export default function FlowPage({ flow }: { flow: FlowType }) { onDrop={onDrop} onNodesDelete={onDelete} onSelectionChange={onSelectionChange} + nodesDraggable={!disableCopyPaste} + panOnDrag={!disableCopyPaste} selectNodesOnDrag={false} > diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index e660e1c4d..e7559023a 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -69,6 +69,7 @@ export type DisclosureComponentType = { export type FloatComponentType = { value: string; disabled?: boolean; + disableCopyPaste?: boolean; onChange: (value: string) => void; };