diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 363415ccf..afef59f3a 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -26,6 +26,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.6", "@tabler/icons-react": "^2.32.0", "@tailwindcss/forms": "^0.5.6", @@ -2761,6 +2762,31 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", + "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 053a246ee..39d595dcd 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.6", "@tabler/icons-react": "^2.32.0", "@tailwindcss/forms": "^0.5.6", diff --git a/src/frontend/src/components/tableComponent/components/tableToolbar.tsx/index.tsx b/src/frontend/src/components/tableComponent/components/tableToolbar.tsx/index.tsx new file mode 100644 index 000000000..bbedbad0e --- /dev/null +++ b/src/frontend/src/components/tableComponent/components/tableToolbar.tsx/index.tsx @@ -0,0 +1,3 @@ +export default function TableToolbar({ children }): JSX.Element { + return
{children}
; +} diff --git a/src/frontend/src/components/tableComponent/index.tsx b/src/frontend/src/components/tableComponent/index.tsx index 534ed171f..1a9c7a44d 100644 --- a/src/frontend/src/components/tableComponent/index.tsx +++ b/src/frontend/src/components/tableComponent/index.tsx @@ -1,7 +1,7 @@ import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid import { AgGridReact, AgGridReactProps } from "ag-grid-react"; -import { ElementRef, forwardRef } from "react"; +import { ElementRef, forwardRef, useRef } from "react"; import { DEFAULT_TABLE_ALERT_MSG, DEFAULT_TABLE_ALERT_TITLE, @@ -11,6 +11,9 @@ import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid import { cn, toTitleCase } from "../../utils/utils"; import ForwardedIconComponent from "../genericIconComponent"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; +import { Toggle } from "../ui/toggle"; +import ShadTooltip from "../shadTooltipComponent"; +import resetGrid from "./utils/reset-grid-columns"; interface TableComponentProps extends AgGridReactProps { columnDefs: NonNullable; @@ -32,27 +35,45 @@ const TableComponent = forwardRef< }, ref, ) => { + const gridRef = useRef(null); + const realRef = ref?.current ? ref : gridRef; const dark = useDarkStore((state) => state.dark); - if (props.rowData.length === 0) { - return ( -
- - - {alertTitle} - {alertDescription} - -
- ); - } - const colDef = props.columnDefs.map((col, index) => { + const makeLastColumnNonResizable = (columnDefs) => { + columnDefs.forEach((colDef, index) => { + colDef.resizable = index !== columnDefs.length - 1; + }); + return columnDefs; + }; + + const onGridReady = (params) => { + realRef.current = params; + const updatedColumnDefs = makeLastColumnNonResizable([ + ...props.columnDefs, + ]); + params.api.setColumnDefs(updatedColumnDefs); + if (props.onGridReady) props.onGridReady(params); + }; + + const onColumnMoved = (params) => { + const updatedColumnDefs = makeLastColumnNonResizable( + params.columnApi.getAllGridColumns().map((col) => col.getColDef()), + ); + params.api.setColumnDefs(updatedColumnDefs); + if (props.onColumnMoved) props.onColumnMoved(params); + }; + + let colDef = props.columnDefs.map((col, index) => { let newCol = { ...col, headerName: toTitleCase(col.headerName), }; + if (index === props.columnDefs.length - 1) { + newCol = { + ...col, + resizable: false, + }; + } if (props.onSelectionChanged && index === 0) { newCol = { ...newCol, @@ -73,12 +94,28 @@ const TableComponent = forwardRef< } return newCol; }); + let rowDef = props.rowData; + if (props.rowData.length === 0) { + return ( +
+ + + {alertTitle} + {alertDescription} + +
+ ); + } return (
+ {/*
+
setShow(!show)} + > + + +
+
*/} +
+ + { + resetGrid(realRef); + }} + > + + + +
); }, diff --git a/src/frontend/src/components/tableComponent/utils/reset-grid-columns.tsx b/src/frontend/src/components/tableComponent/utils/reset-grid-columns.tsx new file mode 100644 index 000000000..1ef2f58a1 --- /dev/null +++ b/src/frontend/src/components/tableComponent/utils/reset-grid-columns.tsx @@ -0,0 +1,5 @@ +export default function resetGrid(ref) { + if (ref?.current && ref?.current.api) { + ref.current.api.resetColumnState(); + } +} diff --git a/src/frontend/src/components/ui/toggle.tsx b/src/frontend/src/components/ui/toggle.tsx new file mode 100644 index 000000000..251103615 --- /dev/null +++ b/src/frontend/src/components/ui/toggle.tsx @@ -0,0 +1,45 @@ +"use client"; + +import * as React from "react"; +import * as TogglePrimitive from "@radix-ui/react-toggle"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../../utils/utils"; + +const toggleVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-transparent", + outline: + "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", + }, + size: { + default: "h-10 px-3", + sm: "h-9 px-2.5", + lg: "h-11 px-5", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +const Toggle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, ...props }, ref) => ( + +)); + +Toggle.displayName = TogglePrimitive.Root.displayName; + +export { Toggle, toggleVariants }; diff --git a/src/frontend/src/modals/IOModal/components/chatView/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/index.tsx index 8bc226074..6b122cf4c 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/index.tsx @@ -138,7 +138,7 @@ export default function ChatView({ function updateChat( chat: ChatMessageType, message: string, - stream_url?: string + stream_url?: string, ) { // if (message === "") return; chat.message = message; @@ -177,7 +177,7 @@ export default function ChatView({ name="Eraser" className={classNames( "h-5 w-5 transition-all duration-100", - lockChat ? "animate-pulse text-primary" : "text-primary" + lockChat ? "animate-pulse text-primary" : "text-primary", )} aria-hidden="true" /> diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx index f4de52cbb..d7f4d5202 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/hooks/use-remove-messages.tsx @@ -5,7 +5,7 @@ const useRemoveMessages = ( setSelectedRows, setSuccessData, setErrorData, - selectedRows + selectedRows, ) => { const deleteMessages = useMessagesStore((state) => state.removeMessages); diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index e2c81eff6..8554d2f55 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -79,7 +79,7 @@ const useFlowStore = create((set, get) => ({ updateFlowPool: ( nodeId: string, data: FlowPoolObjectType | ChatOutputType | chatInputType, - buildId?: string + buildId?: string, ) => { let newFlowPool = cloneDeep({ ...get().flowPool }); if (!newFlowPool[nodeId]) { @@ -170,7 +170,7 @@ const useFlowStore = create((set, get) => ({ flowsManager.autoSaveCurrentFlow( newChange, newEdges, - get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, ); } }, @@ -186,7 +186,7 @@ const useFlowStore = create((set, get) => ({ flowsManager.autoSaveCurrentFlow( get().nodes, newChange, - get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, ); } }, @@ -204,7 +204,7 @@ const useFlowStore = create((set, get) => ({ return newChange; } return node; - }) + }), ); }, getNode: (id: string) => { @@ -215,8 +215,8 @@ const useFlowStore = create((set, get) => ({ get().nodes.filter((node) => typeof nodeId === "string" ? node.id !== nodeId - : !nodeId.includes(node.id) - ) + : !nodeId.includes(node.id), + ), ); }, deleteEdge: (edgeId) => { @@ -224,8 +224,8 @@ const useFlowStore = create((set, get) => ({ get().edges.filter((edge) => typeof edgeId === "string" ? edge.id !== edgeId - : !edgeId.includes(edge.id) - ) + : !edgeId.includes(edge.id), + ), ); }, paste: (selection, position) => { @@ -291,7 +291,7 @@ const useFlowStore = create((set, get) => ({ let source = idsMap[edge.source]; let target = idsMap[edge.target]; const sourceHandleObject: sourceHandleType = scapeJSONParse( - edge.sourceHandle! + edge.sourceHandle!, ); let sourceHandle = scapedJSONStringfy({ ...sourceHandleObject, @@ -301,7 +301,7 @@ const useFlowStore = create((set, get) => ({ edge.data.sourceHandle = sourceHandleObject; const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! + edge.targetHandle!, ); let targetHandle = scapedJSONStringfy({ ...targetHandleObject, @@ -322,7 +322,7 @@ const useFlowStore = create((set, get) => ({ className: "stroke-gray-900 ", selected: false, }, - newEdges.map((edge) => ({ ...edge, selected: false })) + newEdges.map((edge) => ({ ...edge, selected: false })), ); }); get().setEdges(newEdges); @@ -341,10 +341,10 @@ const useFlowStore = create((set, get) => ({ }); const newNodes = get().nodes.filter( - (node) => !nodesIdsSelected.includes(node.id) + (node) => !nodesIdsSelected.includes(node.id), ); const newEdges = get().edges.filter( - (edge) => !edgesIdsSelected.includes(edge.id) + (edge) => !edgesIdsSelected.includes(edge.id), ); set({ nodes: newNodes, edges: newEdges }); @@ -402,7 +402,7 @@ const useFlowStore = create((set, get) => ({ style: { stroke: "#555" }, className: "stroke-foreground stroke-connection", }, - oldEdges + oldEdges, ); return newEdges; @@ -412,7 +412,7 @@ const useFlowStore = create((set, get) => ({ .autoSaveCurrentFlow( get().nodes, newEdges, - get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, ); }, unselectAll: () => { @@ -443,7 +443,7 @@ const useFlowStore = create((set, get) => ({ function validateSubgraph(nodes: string[]) { const errorsObjs = validateNodes( get().nodes.filter((node) => nodes.includes(node.id)), - get().edges + get().edges, ); const errors = errorsObjs.map((obj) => obj.errors).flat(); @@ -462,14 +462,14 @@ const useFlowStore = create((set, get) => ({ function handleBuildUpdate( vertexBuildData: VertexBuildTypeAPI, status: BuildStatus, - runId: string + runId: string, ) { console.log("handleBuildUpdate", vertexBuildData, status, runId); if (vertexBuildData && vertexBuildData.inactivated_vertices) { get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices); get().updateBuildStatus( vertexBuildData.inactivated_vertices, - BuildStatus.INACTIVE + BuildStatus.INACTIVE, ); } @@ -485,14 +485,15 @@ const useFlowStore = create((set, get) => ({ // next_vertices_ids should be next_vertices_ids without the inactivated vertices const next_vertices_ids = vertexBuildData.next_vertices_ids.filter( - (id) => !vertexBuildData.inactivated_vertices?.includes(id) + (id) => !vertexBuildData.inactivated_vertices?.includes(id), ); const top_level_vertices = vertexBuildData.top_level_vertices.filter( - (vertex) => !vertexBuildData.inactivated_vertices?.includes(vertex.id) + (vertex) => + !vertexBuildData.inactivated_vertices?.includes(vertex.id), ); const nextVertices: VertexLayerElementType[] = zip( next_vertices_ids, - top_level_vertices + top_level_vertices, ).map(([id, reference]) => ({ id: id!, reference })); const newLayers = [ @@ -514,7 +515,7 @@ const useFlowStore = create((set, get) => ({ get().addDataToFlowPool( { ...vertexBuildData, buildId: runId }, - vertexBuildData.id + vertexBuildData.id, ); useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status); @@ -523,7 +524,7 @@ const useFlowStore = create((set, get) => ({ const newFlowBuildStatus = { ...get().flowBuildStatus }; // filter out the vertices that are not status const verticesToUpdate = verticesIds?.filter( - (id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT + (id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT, ); if (verticesToUpdate) { @@ -588,7 +589,7 @@ const useFlowStore = create((set, get) => ({ verticesLayers: VertexLayerElementType[][]; runId: string; verticesToRun: string[]; - } | null + } | null, ) => { set({ verticesBuild: vertices }); }, @@ -613,7 +614,7 @@ const useFlowStore = create((set, get) => ({ // that are going to be built verticesIds: get().verticesBuild!.verticesIds.filter( // keep the vertices that are not in the list of vertices to remove - (vertex) => !vertices.includes(vertex) + (vertex) => !vertices.includes(vertex), ), }, }); diff --git a/src/frontend/src/style/classes.css b/src/frontend/src/style/classes.css index 39b080390..29140e7fa 100644 --- a/src/frontend/src/style/classes.css +++ b/src/frontend/src/style/classes.css @@ -15,6 +15,10 @@ pre { font-family: inherit; } +.ag-paging-page-size { + display: none; +} + .react-flow__pane { cursor: default; } diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index c421511de..6f995f5ec 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -78,7 +78,7 @@ export type FlowStoreType = { state: | FlowState | undefined - | ((oldState: FlowState | undefined) => FlowState) + | ((oldState: FlowState | undefined) => FlowState), ) => void; nodes: Node[]; edges: Edge[]; @@ -86,11 +86,11 @@ export type FlowStoreType = { onEdgesChange: OnEdgesChange; setNodes: ( update: Node[] | ((oldState: Node[]) => Node[]), - skipSave?: boolean + skipSave?: boolean, ) => void; setEdges: ( update: Edge[] | ((oldState: Edge[]) => Edge[]), - skipSave?: boolean + skipSave?: boolean, ) => void; setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; getNode: (id: string) => Node | undefined; @@ -98,12 +98,12 @@ export type FlowStoreType = { deleteEdge: (edgeId: string | Array) => void; paste: ( selection: { nodes: any; edges: any }, - position: { x: number; y: number; paneX?: number; paneY?: number } + position: { x: number; y: number; paneX?: number; paneY?: number }, ) => void; lastCopiedSelection: { nodes: any; edges: any } | null; setLastCopiedSelection: ( newSelection: { nodes: any; edges: any } | null, - isCrop?: boolean + isCrop?: boolean, ) => void; cleanFlow: () => void; setFilterEdge: (newState) => void; @@ -127,7 +127,7 @@ export type FlowStoreType = { verticesLayers: VertexLayerElementType[][]; runId: string; verticesToRun: string[]; - } | null + } | null, ) => void; addToVerticesBuild: (vertices: string[]) => void; removeFromVerticesBuild: (vertices: string[]) => void; @@ -145,7 +145,7 @@ export type FlowStoreType = { updateFlowPool: ( nodeId: string, data: FlowPoolObjectType | ChatOutputType | chatInputType, - buildId?: string + buildId?: string, ) => void; getNodePosition: (nodeId: string) => { x: number; y: number }; }; diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts index 637e4331a..c8391b211 100644 --- a/src/frontend/src/utils/storeUtils.ts +++ b/src/frontend/src/utils/storeUtils.ts @@ -7,7 +7,7 @@ export default function cloneFLowWithParent( flow: FlowType, parent: string, is_component: boolean, - keepId = false + keepId = false, ) { let childFLow = cloneDeep(flow); childFLow.parent = parent; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index c73ec5d23..b1b6a6f25 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -143,6 +143,8 @@ import { X, XCircle, Zap, + RotateCcw, + Settings, } from "lucide-react"; import { FaApple, FaDiscord, FaGithub } from "react-icons/fa"; import { AWSIcon } from "../icons/AWS"; @@ -526,4 +528,6 @@ export const nodeIconsLucide: iconsType = { FolderPlusIcon, FolderIcon, Discord: FaDiscord, + RotateCcw, + Settings, };