Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4c98bb7
merge fix
TamarZanzouri Nov 19, 2025
cd92075
show flex stacker step toolbar
TamarZanzouri Nov 20, 2025
a1be733
added stacker options dd
TamarZanzouri Nov 20, 2025
17db914
stacker options
TamarZanzouri Nov 20, 2025
ec84ede
styling dd
TamarZanzouri Nov 20, 2025
0dae439
WIP labware details component
TamarZanzouri Nov 21, 2025
679f249
added stories and styling for labware details with count
TamarZanzouri Nov 21, 2025
9a6b266
styling
TamarZanzouri Nov 24, 2025
2229ff1
wire up component for labware details
TamarZanzouri Nov 25, 2025
dc7fe5a
nuber of labware in hopper
TamarZanzouri Nov 25, 2025
7a6f9fb
deafult state for step
TamarZanzouri Nov 25, 2025
038b3c0
update state for step
TamarZanzouri Nov 26, 2025
0f8ff18
no labware on shuttle
TamarZanzouri Nov 26, 2025
92e86c8
command options
TamarZanzouri Nov 26, 2025
fe2cee8
added tests WIP and conditions for radio button
TamarZanzouri Nov 26, 2025
94621f8
extract logic to container
TamarZanzouri Dec 1, 2025
3c6051d
added basic tests and container for stacker tool
TamarZanzouri Dec 1, 2025
df9a739
vertical align and step name
TamarZanzouri Dec 2, 2025
a297c52
translate title name instead of using step type
TamarZanzouri Dec 2, 2025
38e6db2
translations in step form and refactor to component labware mount
TamarZanzouri Dec 2, 2025
72bc09a
styling and refactoring labware in stacker
TamarZanzouri Dec 2, 2025
705738c
show count only if there is and shuttle translations
TamarZanzouri Dec 2, 2025
f81982e
shuttle translation and logic quantitny null
TamarZanzouri Dec 2, 2025
0b16f8f
translate module controls
TamarZanzouri Dec 2, 2025
b16ff04
styling fixes
TamarZanzouri Dec 3, 2025
77884e1
added tests
TamarZanzouri Dec 3, 2025
ec51827
console.log
TamarZanzouri Dec 3, 2025
ae46454
labwareDetailsWithCount styling and logic fixes
TamarZanzouri Dec 3, 2025
34171d7
flexStackerTools css
TamarZanzouri Dec 3, 2025
2b433d9
capitalize method if not stacker
TamarZanzouri Dec 3, 2025
a25e1aa
formatting fixes
TamarZanzouri Dec 3, 2025
844e753
refactor and formatting
TamarZanzouri Dec 3, 2025
21ce32f
refactor labware details component
TamarZanzouri Dec 4, 2025
e755165
linting and small fixes
TamarZanzouri Dec 5, 2025
8abc0bb
css fixes
TamarZanzouri Dec 5, 2025
913c97b
Update protocol-designer/src/step-forms/utils/createPresavedStepForm.ts
TamarZanzouri Dec 5, 2025
5edf09a
linting
TamarZanzouri Dec 5, 2025
98f4557
linting and other test fixes
TamarZanzouri Dec 5, 2025
e852f6c
test fixes
TamarZanzouri Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"pickup_tip": "Picking up tip(s) from {{well_range}} of {{labware}} in {{labware_location}}",
"prepare_to_aspirate": "Preparing {{pipette}} to aspirate",
"pressurizing_to_dispense": "Pressurize pipette to dispense {{volume}} µL from resin tip at {{flow_rate}} µL/sec",
"quantity": "Quantity: {{count}}",
"reloading_labware": "Reloading {{labware}}",
"return_tip": "Returning tip to {{well_name}} of {{labware}} in {{labware_location}}",
"right": "Right",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
div.container {
width: 318px;
padding: var(--spacing-16) var(--spacing-8);
border-radius: var(--border-radius-4);
background-color: var(--grey-20);
}

div.sub_title {
display: flex;
width: 100%;
flex-direction: column;
color: var(--grey-60);
gap: var(--spacing-8);
}

div.label {
display: flex;
width: 88px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: var(--border-radius-4);
background-color: var(--transparent-black-80);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { LabwareDetailsWithCount } from './index'

import type { Meta, StoryObj } from '@storybook/react'

const meta: Meta<typeof LabwareDetailsWithCount> = {
title: 'Helix/Organisms/LabwareDetailsWithCount',
component: LabwareDetailsWithCount,
decorators: [
Story => (
<div>
<Story />
</div>
),
],
}
export default meta

type Story = StoryObj<typeof LabwareDetailsWithCount>

export const LabwareDetailsWithCountStory: Story = {
args: {
title: 'Opentrons Flex 96 Tip Rack 1000 µL',
subTitle: 'With tip rack lid',
quantity: 1,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useTranslation } from 'react-i18next'
import { screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { LabwareDetailsWithCount } from '..'
import { renderWithProviders } from '../../../testing/utils'

import type { ComponentProps } from 'react'

vi.mock('react-i18next', () => ({
useTranslation: vi.fn(),
initReactI18next: vi.fn(),
}))

vi.mock('i18next', () => {
return {
default: {
use: () => ({ init: vi.fn() }),
createInstance: () => ({
use: () => ({ init: vi.fn() }),
init: vi.fn(),
t: (k: string) => k,
}),
init: vi.fn(),
t: (k: string) => k,
},
}
})
const render = (props: ComponentProps<typeof LabwareDetailsWithCount>) => {
return renderWithProviders(<LabwareDetailsWithCount {...props} />)
}
describe('LabwareDetailsWithCount', () => {
let props: ComponentProps<typeof LabwareDetailsWithCount>
const t = vi.fn(key => key)
beforeEach(() => {
props = {
title: 'Title',
subTitle: 'SubTitle',
quantity: 1,
}
vi.mocked(useTranslation).mockReturnValue({ t } as any)
})

it('should render title, subTitle and label', () => {
render(props)
expect(screen.getByText('Title')).toBeInTheDocument()
expect(screen.getByText('SubTitle')).toBeInTheDocument()
expect(screen.getByText('quantity')).toBeInTheDocument()
})

it('should render title without subTitle and label', () => {
props.subTitle = undefined
props.quantity = undefined
render(props)
expect(screen.getByText('Title')).toBeInTheDocument()
expect(screen.queryByText('SubTitle')).not.toBeInTheDocument()
expect(screen.queryByText('quantity')).not.toBeInTheDocument()
})
})
31 changes: 31 additions & 0 deletions components/src/organisms/LabwareDetailsWithCount/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useTranslation } from 'react-i18next'

import { StyledText, Tag } from '../../atoms'
import styles from './LabwareDetailsWithCount.module.css'

interface LabwareDetailsWithCountProps {
title: string
subTitle?: string
quantity?: number
}

export function LabwareDetailsWithCount({
title,
subTitle,
quantity: label,
}: LabwareDetailsWithCountProps): JSX.Element {
const { t } = useTranslation('protocol_command_text')
return (
<div className={styles.container}>
<StyledText desktopStyle="bodyDefaultRegular">{title}</StyledText>
<div className={styles.subTitle}>
<StyledText desktopStyle="bodyDefaultRegular">{subTitle}</StyledText>
</div>
{label != null ? (
<div className={styles.label}>
<Tag type="default" text={t('quantity', { count: label })} />
</div>
) : null}
</div>
)
}
1 change: 1 addition & 0 deletions components/src/organisms/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './CommandText'
export * from './DeckLabelSet'
export * from './FixtureOption'
export * from './LabwareDetailsWithCount'
export * from './LabwareInfoOverlay'
export * from './ProtocolDeck'
export * from './Toolbox'
11 changes: 6 additions & 5 deletions protocol-designer/src/assets/localization/en/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,26 @@
"pipettes": "Pipettes",
"protocol_name": "Protocol Name",
"save": "save",
"select": "Select",
"selected": "Selected",
"source": "Source",
"stepType": {
"absorbanceReader": "absorbance plate reader",
"camera": "camera",
"comment": "comment",
"ending_hold": "ending hold",
"flexStacker": "flex stacker",
"heaterShaker": "heater-shaker",
"magnet": "magnet",
"mix": "mix",
"moveLabware": "move",
"moveLiquid": "transfer",
"pause": "pause",
"profile_steps": "profile steps",
"profile": "Program a Thermocycler profile",
"profile_steps": "profile steps",
"temperature": "temperature",
"thermocycler": "thermocycler"
},
"select": "Select",
"selected": "Selected",
"source": "Source",
"temperature": "Temperature (°C)",
"time": "Time",
"units": {
Expand All @@ -60,8 +61,8 @@
"microliterPerSec": "µL/s",
"millimeter": "mm",
"millimeterPerSec": "mm/s",
"nanometer": "nm",
"minutes": "m",
"nanometer": "nm",
"rpm": "rpm",
"seconds": "s",
"seconds_long": "seconds",
Expand Down
51 changes: 37 additions & 14 deletions protocol-designer/src/assets/localization/en/protocol_steps.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"blowout_location": "Blowout location",
"blowout_position": "Blowout position from top",
"bottom_of_stack": "Bottom of stack",
"camera": {
"capture_image": "Capture image of the deck"
},
"captions_for_fields": {
"blockTargetTemp": "Valid range between 4 and 99 °C",
"blockTargetTempHold": "Valid range between 4 and 99 °C",
Expand All @@ -36,9 +39,6 @@
"targetTemperature": "Valid range between 4 and 95 °C",
"volume": "Recommended between 0.1 and {{max}}"
},
"camera": {
"capture_image": "Capture image of the deck"
},
"change_tips": "Change tips",
"column": "Column",
"comfirm_reset_settings": {
Expand Down Expand Up @@ -67,6 +67,29 @@
"edit_step": "Edit step",
"ending_deck": "Ending deck",
"engage_height": "Engage height",
"flex_stacker": {
"label": "Flex Stacker",
"module_controls": {
"empty_label": "Empty",
"empty_sublabel": "Manually empty all labware from the stacker",
"label": "Module controls",
"refill_label": "Refill",
"refill_sublabel": "Refill the stacker with labware. Manually fill the stacker with more labware",
"retrieve_label": "Retrieve",
"retrieve_sublabel": "Retrieve labware from the stacker onto the shuttle"
},
"shuttle": {
"label": "Shuttle",
"no_labware": "No labware on shuttle"
},
"stacker": {
"label": "Stacker",
"labware_filled": "{{amount}}/{{total}} labware filled",
"no_labware": "No labware stored on stacker",
"quantity": "Quantity: {{count}}"
}
},
"flexStacker": "Stacker",
"flow_rate_builder": "Flow rate builder",
"flow_type_title": "{{type}} flow rate",
"from": "from",
Expand Down Expand Up @@ -97,8 +120,8 @@
},
"heater_shaker_state": "Heater-Shaker state",
"in": "in",
"into": "into",
"individual_wells": "Individual wells",
"into": "into",
"labware_in": "Labware in",
"labware_to": "{{labware}} to",
"liquids": "{{num}} liquids",
Expand Down Expand Up @@ -127,12 +150,12 @@
"of": "of",
"off_deck": "Off-Deck",
"pause": {
"forDuration": "For {{duration}}",
"pausingForDuration": "<text>Pausing for</text><tag/>",
"pausingUntilResume": "Pausing until manually told to resume",
"pausingUntilTemperature": "<text>Pausing until</text><semiBoldText>{{module}}</semiBoldText><text>reaches</text><tag/>",
"pausingForDuration": "<text>Pausing for</text><tag/>",
"untilResume": "Until told to resume",
"untilTemperature": "Until {{temperature}} °C reached",
"forDuration": "For {{duration}}"
"untilTemperature": "Until {{temperature}} °C reached"
},
"pipette": "Pipette",
"pipette_path": "Pipette path",
Expand Down Expand Up @@ -198,11 +221,15 @@
"block_value": "{{value}} °C",
"block_value_off": "Off",
"lid_label": "Lid set to",
"lid_value": "{{value}} °C",
"lid_value_off": "Off",
"lid_position_label": "Lid position",
"lid_position_value_closed": "Closed",
"lid_position_value_open": "Open",
"lid_position_value_closed": "Closed"
"lid_value": "{{value}} °C",
"lid_value_off": "Off"
},
"profile_timeline": {
"start": "Start profile",
"wait_for_complete": "Wait for profile to complete"
},
"repeat": "Repeat {{repetitions}} times",
"substep_settings": "<text>Set block temperature to</text><tagTemperature/><text>for</text><tagDuration/>",
Expand All @@ -218,10 +245,6 @@
"block": "<text>Set thermocycler block to</text><tag/>",
"lid_position": "<text>Lid position</text><tag/>",
"lid_temperature": "<text>Set thermocycler lid to</text><tag/>"
},
"profile_timeline": {
"start": "Start profile",
"wait_for_complete": "Wait for profile to complete"
}
},
"time": "Time",
Expand Down
10 changes: 10 additions & 0 deletions protocol-designer/src/form-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export type StepType =
| 'pause'
| 'temperature'
| 'thermocycler'
| 'flexStacker'

export const stepIconsByType: Record<StepType, IconName> = {
absorbanceReader: 'ot-absorbance',
Expand All @@ -186,6 +187,7 @@ export const stepIconsByType: Record<StepType, IconName> = {
temperature: 'ot-temperature-v2',
thermocycler: 'ot-thermocycler',
heaterShaker: 'ot-heater-shaker',
flexStacker: 'ot-flex-stacker',
}
// ===== Unprocessed form types =====
export interface AnnotationFields {
Expand Down Expand Up @@ -498,6 +500,13 @@ export interface HydratedAbsorbanceReaderFormData extends AnnotationFields {
wavelengths: string[]
}

// TODO(TZ, 2025-12-03): not fully flushed out, but this is the initial hydrated form data for the flex stacker form
export interface HydratedFlexStackerFormData extends AnnotationFields {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're missing fields here. This should include the types of all the properties in protocol-designer/src/steplist/formLevel/getDefaultsForStepType.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left a comment about not being fully flushed

stepType: 'flexStacker'
id: string
moduleId: string
}

// fields used in TipPositionInput
export type TipZOffsetFields =
| 'aspirate_mmFromBottom'
Expand Down Expand Up @@ -616,3 +625,4 @@ export type HydratedFormData =
| HydratedPauseFormData
| HydratedTemperatureFormData
| HydratedThermocyclerFormData
| HydratedFlexStackerFormData
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
AbsorbanceReaderTools,
CameraTools,
CommentTools,
FlexStackerToolsContainer,
HeaterShakerTools,
MagnetTools,
MixTools,
Expand Down Expand Up @@ -107,6 +108,7 @@ const STEP_FORM_MAP: StepFormMap = {
comment: CommentTools,
camera: CameraTools,
absorbanceReader: AbsorbanceReaderTools,
flexStacker: FlexStackerToolsContainer,
}

// used to inform StepFormToolbox when to prompt user confirmation for overriding advanced settings
Expand Down Expand Up @@ -535,7 +537,10 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element {
desktopStyle="bodyLargeSemiBold"
css={LINE_CLAMP_TEXT_STYLE(2, true)}
>
{capitalizeFirstLetter(String(formData.stepName))}
{/* TODO: use module object from form.json instead */}
{formData.stepType === 'flexStacker'
? t(`protocol_steps:${formData.stepType}`)
: capitalizeFirstLetter(String(formData.stepName))}
Comment on lines +540 to +543
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry why do we need to special case the stacker? it could be right I am just not following

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to move away from capitalizeFirstLetter and use the step type as the key in a translation file. so instead of special casing this in capitalizeFirstLetter I special cased it here.

</StyledText>
</Flex>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.space_between {
display: flex;
justify-content: space-between;
}

div.container {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need div here since we're adding these styles to a div in the component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I added the div bc the I wanted to make sure that this class is being appended to a div but its also a local css file so its not really a MUST

display: flex;
width: 100%;
flex-direction: column;
padding-top: var(--spacing-16);
gap: var(--spacing-8);
}

.padding_x {
padding: 0 var(--spacing-16);
}
Loading
Loading