Skip to content

Commit

Permalink
Opentrons ai client resizer (#16792)
Browse files Browse the repository at this point in the history
# Overview

This PR adds a resize bar to control the create protocol page columns
width.


![image](https://github.com/user-attachments/assets/bb6bd43e-c593-4f05-80c5-504cf471c031)

## Test Plan and Hands on Testing

- On the landing page click Create a new protocol button, you will be
redirected to the new page
- You can now drag the resize bar to adjust the columns width

## Changelog

 - Add resize bar

## Review requests

- Verify resize bar.

## Risk assessment

- low
  • Loading branch information
fbelginetw authored Nov 13, 2024
1 parent 28261dc commit 910c81e
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 28 deletions.
59 changes: 59 additions & 0 deletions opentrons-ai-client/src/atoms/ResizeBar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
COLORS,
DISPLAY_FLEX,
POSITION_ABSOLUTE,
POSITION_RELATIVE,
} from '@opentrons/components'

export function ResizeBar({
handleMouseDown,
}: {
handleMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void
}): JSX.Element {
return (
<div
style={{
width: '3px',
cursor: 'col-resize',
backgroundColor: COLORS.grey30,
height: '100%',
position: POSITION_RELATIVE,
}}
onMouseDown={handleMouseDown}
>
<div
style={{
width: '16px',
height: '24px',
backgroundColor: COLORS.grey30,
borderRadius: '16px',
position: POSITION_ABSOLUTE,
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display: DISPLAY_FLEX,
alignItems: 'center',
justifyContent: 'center',
gap: '3px',
}}
>
<div
style={{
width: '2px',
height: '10px',
borderRadius: '12px',
backgroundColor: COLORS.white,
}}
/>
<div
style={{
width: '2px',
height: '10px',
borderRadius: '12px',
backgroundColor: COLORS.white,
}}
/>
</div>
</div>
)
}
3 changes: 1 addition & 2 deletions opentrons-ai-client/src/molecules/PromptPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ interface PromptPreviewProps {
const PromptPreviewContainer = styled(Flex)`
flex-direction: ${DIRECTION_COLUMN};
width: 100%;
max-width: 516px;
height: ${SIZE_AUTO};
padding-top: ${SPACING.spacing8};
background-color: ${COLORS.transparent};
Expand Down Expand Up @@ -79,7 +78,7 @@ export function PromptPreview({
key={`section-${index}`}
title={section.title}
items={section.items}
itemMaxWidth={index <= 1 ? '33.33%' : '100%'}
itemMaxWidth={index === 1 ? '50%' : '100%'}
oneItemPerRow={index === 4}
/>
)
Expand Down
125 changes: 99 additions & 26 deletions opentrons-ai-client/src/pages/CreateProtocol/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
SPACING,
} from '@opentrons/components'
import { useTranslation } from 'react-i18next'
import { useEffect } from 'react'
import { useEffect, useRef, useState } from 'react'
import { PromptPreview } from '../../molecules/PromptPreview'
import { useForm, FormProvider } from 'react-hook-form'
import {
Expand All @@ -24,6 +24,7 @@ import type { DisplayModules } from '../../organisms/ModulesSection'
import type { DisplayLabware } from '../../organisms/LabwareLiquidsSection'
import { useNavigate } from 'react-router-dom'
import { useTrackEvent } from '../../resources/hooks/useTrackEvent'
import { ResizeBar } from '../../atoms/ResizeBar'

export interface CreateProtocolFormData {
application: {
Expand Down Expand Up @@ -54,6 +55,12 @@ export function CreateProtocol(): JSX.Element | null {
const [, setUpdateProtocolChatAtom] = useAtom(updateProtocolChatAtom)
const navigate = useNavigate()
const trackEvent = useTrackEvent()
const [leftWidth, setLeftWidth] = useState(50)
const [isResizing, setIsResizing] = useState(false)
const [initialMouseX, setInitialMouseX] = useState(0)
const [initialLeftWidth, setInitialLeftWidth] = useState(50)

const parentRef = useRef<HTMLDivElement>(null)

const methods = useForm<CreateProtocolFormData>({
defaultValues: {
Expand All @@ -70,10 +77,6 @@ export function CreateProtocol(): JSX.Element | null {
},
})

function calculateProgress(): number {
return currentStep > 0 ? currentStep / TOTAL_STEPS : 0
}

// Reset the update protocol chat atom when navigating to the create protocol page
useEffect(() => {
setUpdateProtocolChatAtom({
Expand Down Expand Up @@ -109,37 +112,107 @@ export function CreateProtocol(): JSX.Element | null {
}
}, [])

useEffect(() => {
if (parentRef.current != null) {
const parentWidth = parentRef.current.offsetWidth
const initialRightWidth = 516 // Initial width of the right column in pixels
const initialLeftWidthPercentage =
((parentWidth - initialRightWidth) / parentWidth) * 100
setLeftWidth(initialLeftWidthPercentage)
}
}, [])

useEffect(() => {
if (isResizing) {
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseup', handleMouseUp)
} else {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseup', handleMouseUp)
}

return () => {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseup', handleMouseUp)
}
}, [isResizing])

function calculateProgress(): number {
return currentStep > 0 ? currentStep / TOTAL_STEPS : 0
}

function handleMouseDown(
e: React.MouseEvent<HTMLDivElement, MouseEvent>
): void {
setIsResizing(true)
setInitialMouseX(e.clientX)
setInitialLeftWidth(leftWidth)
}

function handleMouseMove(e: MouseEvent): void {
if (parentRef.current != null) {
const parentWidth = parentRef.current.offsetWidth
const maxLeftWidth = 75
const minLeftWidth = 25

let newLeftWidth =
initialLeftWidth + ((e.clientX - initialMouseX) / parentWidth) * 100

if (newLeftWidth < minLeftWidth) {
newLeftWidth = minLeftWidth
}

if (newLeftWidth > maxLeftWidth) {
newLeftWidth = maxLeftWidth
}

setLeftWidth(newLeftWidth)
}
}

function handleMouseUp(): void {
setIsResizing(false)
}

function handleSubmit(): void {
const chatPromptData = generateChatPrompt(
methods.getValues(),
t,
setCreateProtocolChatAtom
)

trackEvent({
name: 'submit-prompt',
properties: {
prompt: chatPromptData,
},
})

navigate('/chat')
}

return (
<FormProvider {...methods}>
<Flex
ref={parentRef}
position={POSITION_RELATIVE}
justifyContent={JUSTIFY_SPACE_EVENLY}
gap={SPACING.spacing32}
margin={`${SPACING.spacing16} ${SPACING.spacing16}`}
height="100%"
width="100%"
>
<ProtocolSectionsContainer />
<PromptPreview
handleSubmit={() => {
const chatPromptData = generateChatPrompt(
methods.getValues(),
t,
setCreateProtocolChatAtom
)

trackEvent({
name: 'submit-prompt',
properties: {
prompt: chatPromptData,
},
})

navigate('/chat')
}}
isSubmitButtonEnabled={currentStep === TOTAL_STEPS}
promptPreviewData={generatePromptPreviewData(methods.watch, t)}
/>
<div style={{ width: `${leftWidth}%`, height: '100%' }}>
<ProtocolSectionsContainer />
</div>
<ResizeBar handleMouseDown={handleMouseDown} />
<div style={{ width: `${100 - leftWidth}%`, height: '100%' }}>
<PromptPreview
handleSubmit={handleSubmit}
isSubmitButtonEnabled={currentStep === TOTAL_STEPS}
promptPreviewData={generatePromptPreviewData(methods.watch, t)}
/>
</div>
</Flex>
</FormProvider>
)
Expand Down

0 comments on commit 910c81e

Please sign in to comment.