Skip to content

Commit

Permalink
fixed the dropdown menu and added error states to file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
connected-znaim committed Oct 24, 2024
1 parent f126f11 commit 17fc268
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"application": "Application: Your protocol's name, describing what it does.",
"commands": "Commands: List the protocol's steps, specifying quantities in microliters (uL) and giving exact source and destination locations.",
"copy_code": "Copy code",
"choose_file": "Choose file",
"disclaimer": "OpentronsAI can make mistakes. Review your protocol before running it on an Opentrons robot.",
"drag_and_drop": "Drag and drop or <a>browse</a> your files",
"example": "For example prompts, click the buttons in the left panel.",
"file_length_error": "The length of the file contents is 0. Please upload a file with content.",
"got_feedback": "Got feedback? We love to hear it.",
"key_info": "Here are some key pieces of information to provide in your prompt:",
"labware_and_tipracks": "Labware and tip racks: Use names from the <a>Opentrons Labware Library</a>.",
Expand All @@ -25,6 +27,7 @@
"pipettes": "Pipettes: Specify your pipettes, including the volume, number of channels, and whether they’re mounted on the left or right.",
"protocol_file": "Protocol file",
"provide_details_of_changes": "Provide details of changes you want to make",
"python_file_type_error": "Python file type required",
"reagent_transfer_flex": "Reagent Transfer (Flex)",
"reagent_transfer": "Reagent Transfer",
"reload_page": "To start over and create a new protocol, simply reload the page.",
Expand Down
74 changes: 74 additions & 0 deletions opentrons-ai-client/src/molecules/FileUpload/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { css } from 'styled-components'

import {
ALIGN_CENTER,
BORDERS,
Btn,
COLORS,
DIRECTION_COLUMN,
Flex,
Icon,
JUSTIFY_SPACE_BETWEEN,
SPACING,
LegacyStyledText,
truncateString,
} from '@opentrons/components'

const FILE_UPLOAD_STYLE = css`
&:hover > svg {
background: ${COLORS.black90}${COLORS.opacity20HexCode};
}
&:active > svg {
background: ${COLORS.black90}${COLORS.opacity20HexCode}};
}
`

const FILE_UPLOAD_FOCUS_VISIBLE = css`
&:focus-visible {
border-radius: ${BORDERS.borderRadius4};
box-shadow: 0 0 0 ${SPACING.spacing2} ${COLORS.blue50};
}
`

interface FileUploadProps {
file: File
fileError: string | null
handleClick: () => unknown
}

export function FileUpload({
file,
fileError,
handleClick,
}: FileUploadProps): JSX.Element {
return (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<Btn
onClick={handleClick}
aria-label="remove_file"
css={FILE_UPLOAD_FOCUS_VISIBLE}
>
<Flex
alignItems={ALIGN_CENTER}
backgroundColor={fileError == null ? COLORS.grey20 : COLORS.red30}
borderRadius={BORDERS.borderRadius4}
height={SPACING.spacing44}
justifyContent={JUSTIFY_SPACE_BETWEEN}
padding={SPACING.spacing8}
css={FILE_UPLOAD_STYLE}
>
<LegacyStyledText as="p">
{truncateString(file.name, 34, 19)}
</LegacyStyledText>
<Icon name="close" size="1.5rem" borderRadius="50%" />
</Flex>
</Btn>
{fileError != null ? (
<LegacyStyledText as="label" color={COLORS.red50}>
{fileError}
</LegacyStyledText>
) : null}
</Flex>
)
}
170 changes: 86 additions & 84 deletions opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ import {
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
DropdownField,
Flex,
InputField,
JUSTIFY_CENTER,
JUSTIFY_END,
LargeButton,
StyledText,
Link as LinkComponent,
DropdownMenu,
} from '@opentrons/components'
import type { DropdownOption } from '@opentrons/components'
import { UploadInput } from '../../molecules/UploadInput'
import { HeaderWithMeter } from '../../molecules/HeaderWithMeter'
import { useEffect, useState } from 'react'
import type { ChangeEvent } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { FileUpload } from '../../molecules/FileUpload'

const updateOptions = [
const updateOptions: DropdownOption[] = [
{
name: 'Adapt Python protocol from OT-2 to Flex',
value: 'adapt_python_protocol',
Expand Down Expand Up @@ -47,11 +49,6 @@ const ContentBox = styled(Flex)`
width: 60%;
`

const ContentFlex = styled(Flex)`
flex-direction: ${DIRECTION_COLUMN};
justify-content: ${JUSTIFY_CENTER};
`

const HeadingText = styled(StyledText).attrs({
desktopStyle: 'headingSmallBold',
})``
Expand All @@ -67,61 +64,58 @@ const isValidProtocolFileName = (protocolFileName: string): boolean => {
}

export function UpdateProtocol(): JSX.Element {
const { t } = useTranslation('protocol_generator')
const { t }: { t: (key: string) => string } = useTranslation(
'protocol_generator'
)
const [progressPercentage, setProgressPercentage] = useState<number>(0.0)
const [updateType, setUpdateType] = useState<string>('')
const [updateType, setUpdateType] = useState<DropdownOption | null>(null)
const [detailsValue, setDetailsValue] = useState<string>('')
const [fileValue, setFile] = useState<File | null>(null)
const [pythonText, setPythonTextValue] = useState<string>('')
const [errorText, setErrorText] = useState<string | null>(null)

useEffect(() => {
let progress = 0.0
if (updateType !== '') {
if (updateType !== null) {
progress += 0.33
}

if (detailsValue !== '') {
progress += 0.33
}

if (pythonText !== '') {
if (pythonText !== '' && fileValue !== null && errorText === null) {
progress += 0.34
}

setProgressPercentage(progress)
}, [updateType, detailsValue, pythonText])
}, [updateType, detailsValue, pythonText, errorText, fileValue])

const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
setDetailsValue(event.target.value)
}

const handleUpload = async (
file: File2 & { name: string }
const handleFileUpload = async (
file: File & { name: string }
): Promise<void> => {
if (file.path === null) {
// logger.warn('Failed to upload file, path not found')
}
if (isValidProtocolFileName(file.name)) {
// todo convert to text

console.log('compatible_file_type')
console.log(file.name)
// const text = await new Response(new Blob([await file.text()])).text()
const text = await file.text().catch(error => {
console.error('Error reading file:', error)
setErrorText(t('python_file_read_error'))
})

console.log(text)
if (text) {
if (typeof text === 'string' && text !== '') {
setErrorText(null)
setPythonTextValue(text)
} else {
setErrorText(t('file_length_error'))
}

setFile(file)
} else {
console.log('incompatible_file_type')
setErrorText(t('python_file_type_error'))
setFile(file)
}
// props.onUpload?.()
// trackEvent({
// name: ANALYTICS_IMPORT_PROTOCOL_TO_APP,
// properties: { protocolFileName: file.name },
// })
}

return (
Expand All @@ -131,17 +125,28 @@ export function UpdateProtocol(): JSX.Element {
></HeaderWithMeter>
<Spacer />
<ContentBox>
<ContentFlex>
<HeadingText>{t('update_existing_protocol')}</HeadingText>
<BodyText>{t('protocol_file')}</BodyText>
<Flex
paddingTop="40px"
width="auto"
flexDirection={DIRECTION_ROW}
justifyContent={JUSTIFY_CENTER}
>
<HeadingText>{t('update_existing_protocol')}</HeadingText>
<BodyText>{t('protocol_file')}</BodyText>
<Flex
paddingTop={fileValue !== null ? '8px' : '40px'}
width="auto"
flexDirection={DIRECTION_ROW}
justifyContent={JUSTIFY_CENTER}
>
{fileValue !== null ? (
<Flex width="100%" flexDirection={DIRECTION_COLUMN}>
<FileUpload
file={fileValue}
fileError={errorText}
handleClick={function (): void {
setFile(null)
setErrorText(null)
}}
></FileUpload>
</Flex>
) : (
<UploadInput
uploadButtonText="Choose file"
uploadButtonText={t('choose_file')}
dragAndDropText={
<StyledText as="p">
<Trans
Expand All @@ -161,58 +166,55 @@ export function UpdateProtocol(): JSX.Element {
}
onUpload={async function (file: File) {
try {
await handleUpload(file)
await handleFileUpload(file)
} catch (error) {
// todo perhaps make this a toast?
console.error('Error uploading file:', error)
}
}}
></UploadInput>
</Flex>

<BodyText>{t('type_of_update')}</BodyText>
<DropdownField
value={updateType}
options={updateOptions}
onChange={function (event: ChangeEvent<HTMLSelectElement>): void {
setUpdateType(event.target.value)
/>
)}
</Flex>

<BodyText>{t('type_of_update')}</BodyText>
<Flex flexDirection={DIRECTION_COLUMN} width="100%">
<DropdownMenu
// value={updateType}
width="100%"
dropdownType="neutral"
filterOptions={updateOptions}
currentOption={
updateType ?? {
value: '',
name: 'Select an option',
}
}
onClick={value => {
const selectedOption = updateOptions.find(v => v.value === value)
if (selectedOption != null) {
setUpdateType(selectedOption)
}
}}
/>
<BodyText>{t('provide_details_of_changes')}</BodyText>
<InputField
value={detailsValue}
onChange={handleInputChange}
size="medium"
</Flex>
<BodyText>{t('provide_details_of_changes')}</BodyText>
<InputField
value={detailsValue}
onChange={handleInputChange}
size="medium"
/>
<Flex
paddingTop="40px"
width="auto"
flexDirection={DIRECTION_ROW}
justifyContent={JUSTIFY_END}
>
<LargeButton
disabled={progressPercentage !== 1.0}
buttonText={t('submit_prompt')}
/>
<Flex
paddingTop="40px"
width="auto"
flexDirection={DIRECTION_ROW}
justifyContent={JUSTIFY_END}
>
<LargeButton
disabled={progressPercentage !== 1.0}
buttonText={t('submit_prompt')}
/>
</Flex>
</ContentFlex>
</Flex>
</ContentBox>
</Container>
)
}

interface File2 {
/**
* The real path to the file on the users filesystem
*/
path?: string
/**
* The name of the file
*/
name: string

/**
* The contents of the file
*/
// contents: string
text: () => Promise<string>
}

0 comments on commit 17fc268

Please sign in to comment.