From ab9a0ba31db31580fc7de6f847a3667a88a01675 Mon Sep 17 00:00:00 2001 From: Mykhailo Skorokhodov Date: Thu, 16 Apr 2026 12:10:01 +0200 Subject: [PATCH 1/5] feat: create initial collection prompt + edit name --- .../src/components/laboratory/collections.tsx | 170 ++- .../src/components/laboratory/laboratory.tsx | 302 ++-- .../src/components/laboratory/operation.tsx | 111 +- .../src/components/ui/alert-dialog.tsx | 7 +- .../laboratory/src/components/ui/button.tsx | 2 +- .../laboratory/src/components/ui/combobox.tsx | 275 ---- .../laboratory/src/components/ui/dialog.tsx | 4 +- .../laboratory/src/components/ui/select.tsx | 2 +- .../laboratory/src/lib/collections.ts | 30 +- .../app/src/pages/target-laboratory-new.tsx | 44 + pnpm-lock.yaml | 1300 +++-------------- 11 files changed, 632 insertions(+), 1615 deletions(-) delete mode 100644 packages/libraries/laboratory/src/components/ui/combobox.tsx diff --git a/packages/libraries/laboratory/src/components/laboratory/collections.tsx b/packages/libraries/laboratory/src/components/laboratory/collections.tsx index 9b831d1bfe3..3ec8bd14e5f 100644 --- a/packages/libraries/laboratory/src/components/laboratory/collections.tsx +++ b/packages/libraries/laboratory/src/components/laboratory/collections.tsx @@ -1,12 +1,20 @@ import { useMemo, useState } from 'react'; import { + CheckIcon, FolderIcon, FolderOpenIcon, FolderPlusIcon, + PencilIcon, SearchIcon, TrashIcon, XIcon, } from 'lucide-react'; +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from '@/components/ui/input-group'; import { TooltipTrigger } from '@radix-ui/react-tooltip'; import type { LaboratoryCollection, LaboratoryCollectionOperation } from '../../lib/collections'; import { cn } from '../../lib/utils'; @@ -44,6 +52,7 @@ export const CollectionItem = (props: { collection: LaboratoryCollection }) => { addOperation, setActiveOperation, deleteCollection, + updateCollection, deleteOperationFromCollection, addTab, setActiveTab, @@ -51,67 +60,140 @@ export const CollectionItem = (props: { collection: LaboratoryCollection }) => { } = useLaboratory(); const [isOpen, setIsOpen] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [editedName, setEditedName] = useState(props.collection.name); return ( - - - - - - Are you sure you want to delete collection? - - - {props.collection.name} will be permanently deleted. All operations in this - collection will be deleted as well. - - - - Cancel - + + Edit collection + + )} + {checkPermissions?.('collections:delete') && ( + + + + - - - - - - Delete collection - - )} - + + + + + Are you sure you want to delete collection? + + + {props.collection.name} will be permanently deleted. All operations in + this collection will be deleted as well. + + + + Cancel + + + + + + + + Delete collection + + )} + + + )} {isOpen && diff --git a/packages/libraries/laboratory/src/components/laboratory/laboratory.tsx b/packages/libraries/laboratory/src/components/laboratory/laboratory.tsx index 1194532881e..d57912a2cee 100644 --- a/packages/libraries/laboratory/src/components/laboratory/laboratory.tsx +++ b/packages/libraries/laboratory/src/components/laboratory/laboratory.tsx @@ -645,157 +645,6 @@ export const Laboratory = ( ref={setContainer} > - - - - Update endpoint - Update the endpoint of your laboratory. - -
-
{ - e.preventDefault(); - void updateEndpointForm.handleSubmit(); - }} - > - - - {field => { - const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; - - return ( - field.handleChange(e.target.value)} - aria-invalid={isInvalid} - placeholder="Enter endpoint" - autoComplete="off" - /> - ); - }} - - -
-
- - - - - - -
-
- - - - - Add collection - - Add a new collection of operations to your laboratory. - - -
-
{ - e.preventDefault(); - void addCollectionForm.handleSubmit(); - }} - > - - - {field => { - const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; - return ( - - Name - field.handleChange(e.target.value)} - aria-invalid={isInvalid} - placeholder="Enter name of the collection" - autoComplete="off" - /> - {isInvalid && } - - ); - }} - - -
-
- - - - - - -
-
- - - - Add test - Add a new test to your laboratory. - -
-
{ - e.preventDefault(); - void addTestForm.handleSubmit(); - }} - > - - - {field => { - const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; - return ( - - Name - field.handleChange(e.target.value)} - aria-invalid={isInvalid} - placeholder="Enter name of the test" - autoComplete="off" - /> - {isInvalid && } - - ); - }} - - -
-
- - - - - - -
-
+ + + + Update endpoint + Update the endpoint of your laboratory. + +
+
{ + e.preventDefault(); + void updateEndpointForm.handleSubmit(); + }} + > + + + {field => { + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; + + return ( + field.handleChange(e.target.value)} + aria-invalid={isInvalid} + placeholder="Enter endpoint" + autoComplete="off" + /> + ); + }} + + +
+
+ + + + + + +
+
+ + + + + Add collection + + Add a new collection of operations to your laboratory. + + +
+
{ + e.preventDefault(); + void addCollectionForm.handleSubmit(); + }} + > + + + {field => { + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; + return ( + + Name + field.handleChange(e.target.value)} + aria-invalid={isInvalid} + placeholder="Enter name of the collection" + autoComplete="off" + /> + {isInvalid && } + + ); + }} + + +
+
+ + + + + + +
+
+ + + + Add test + Add a new test to your laboratory. + +
+
{ + e.preventDefault(); + void addTestForm.handleSubmit(); + }} + > + + + {field => { + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; + return ( + + Name + field.handleChange(e.target.value)} + aria-invalid={isInvalid} + placeholder="Enter name of the test" + autoComplete="off" + /> + {isInvalid && } + + ); + }} + + +
+
+ + + + + + +
+
diff --git a/packages/libraries/laboratory/src/components/laboratory/operation.tsx b/packages/libraries/laboratory/src/components/laboratory/operation.tsx index f9ec9a3b9b5..e5915ab92dc 100644 --- a/packages/libraries/laboratory/src/components/laboratory/operation.tsx +++ b/packages/libraries/laboratory/src/components/laboratory/operation.tsx @@ -6,6 +6,7 @@ import { CircleXIcon, ClockIcon, FileTextIcon, + FolderIcon, HistoryIcon, MoreHorizontalIcon, NetworkIcon, @@ -44,7 +45,14 @@ import { DialogTitle, } from '../ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from '../ui/dropdown-menu'; -import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '../ui/empty'; +import { + Empty, + EmptyContent, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from '../ui/empty'; import { Field, FieldGroup, FieldLabel } from '../ui/field'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../ui/resizable'; import { ScrollArea, ScrollBar } from '../ui/scroll-area'; @@ -461,6 +469,7 @@ export const Query = (props: { updateActiveOperation, collections, addOperationToCollection, + openAddCollectionDialog, addHistory, stopActiveOperation, addResponseToHistory, @@ -653,49 +662,67 @@ export const Query = (props: { - Add collection - - Add a new collection of operations to your laboratory. - + Save operation to collection + Save the current operation to a collection.
-
{ - e.preventDefault(); - void saveToCollectionForm.handleSubmit(); - }} - > - - - {field => { - const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; - - return ( - - Collection - - - ); - }} - - -
+ {collections.length > 0 ? ( +
{ + e.preventDefault(); + void saveToCollectionForm.handleSubmit(); + }} + > + + + {field => { + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; + + return ( + + Collection + + + ); + }} + + +
+ ) : ( + + + + + + No collections yet + + You haven't created any collections yet. Get started by adding your first + collection. + + + + + + + )}
diff --git a/packages/libraries/laboratory/src/components/ui/alert-dialog.tsx b/packages/libraries/laboratory/src/components/ui/alert-dialog.tsx index 2acb5d7fad1..85b60829cdb 100644 --- a/packages/libraries/laboratory/src/components/ui/alert-dialog.tsx +++ b/packages/libraries/laboratory/src/components/ui/alert-dialog.tsx @@ -1,5 +1,6 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; import { cn } from '../../lib/utils'; +import { useLaboratory } from '../laboratory/context'; import { buttonVariants } from './button'; function AlertDialog({ ...props }: React.ComponentProps) { @@ -13,7 +14,11 @@ function AlertDialogTrigger({ } function AlertDialogPortal({ ...props }: React.ComponentProps) { - return ; + const { container } = useLaboratory(); + + return ( + + ); } function AlertDialogOverlay({ diff --git a/packages/libraries/laboratory/src/components/ui/button.tsx b/packages/libraries/laboratory/src/components/ui/button.tsx index 845a83693f5..910b9a8641c 100644 --- a/packages/libraries/laboratory/src/components/ui/button.tsx +++ b/packages/libraries/laboratory/src/components/ui/button.tsx @@ -9,7 +9,7 @@ const buttonVariants = cva( variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: - '!text-white hover:bg-destructive/90 focus-visible:ring-destructive/40 bg-destructive/60', + '!text-white !hover:bg-destructive/90 !focus-visible:ring-destructive/40 !bg-destructive/60', outline: 'border shadow-sm hover:text-accent-foreground bg-input/30 border-input hover:bg-input/50', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', diff --git a/packages/libraries/laboratory/src/components/ui/combobox.tsx b/packages/libraries/laboratory/src/components/ui/combobox.tsx deleted file mode 100644 index d915eb34ac6..00000000000 --- a/packages/libraries/laboratory/src/components/ui/combobox.tsx +++ /dev/null @@ -1,275 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { CheckIcon, XIcon } from 'lucide-react'; -import { Combobox as ComboboxPrimitive } from '@base-ui/react'; -import { cn } from '../../lib/utils'; -import { useLaboratory } from '../laboratory/context'; -import { Button } from './button'; -import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './input-group'; - -const Combobox = ComboboxPrimitive.Root; - -function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { - return ; -} - -function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) { - return ( - - {children} - - ); -} - -function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { - return ( - } - className={cn(className)} - {...props} - > - - - ); -} - -function ComboboxInput({ - className, - children, - disabled = false, - showTrigger = true, - showClear = false, - ...props -}: ComboboxPrimitive.Input.Props & { - showTrigger?: boolean; - showClear?: boolean; -}) { - return ( - - } {...props} /> - - {showTrigger && ( - - - - )} - {showClear && } - - {children} - - ); -} - -function ComboboxContent({ - className, - side = 'bottom', - sideOffset = 6, - align = 'start', - alignOffset = 0, - anchor, - ...props -}: ComboboxPrimitive.Popup.Props & - Pick< - ComboboxPrimitive.Positioner.Props, - 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor' - >) { - const { container } = useLaboratory(); - - return ( - - - - - - ); -} - -function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { - return ( - - ); -} - -function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) { - return ( - - {children} - - } - > - - - - ); -} - -function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { - return ( - - ); -} - -function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) { - return ( - - ); -} - -function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { - return ; -} - -function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { - return ( -