Skip to content

Commit

Permalink
feat(protocol-designer): add custom liquid color picker (#10958)
Browse files Browse the repository at this point in the history
* feat(protocol-designer): add custom liquid color picker

This PR adds a custom liquid color picker and field to the liquids page in Protocol Designer. closes #10573, closes #10601

Co-authored-by: smb2268 <[email protected]>
  • Loading branch information
sakibh and smb2268 authored Jul 11, 2022
1 parent da45a96 commit b4a11af
Show file tree
Hide file tree
Showing 32 changed files with 309 additions and 44 deletions.
25 changes: 25 additions & 0 deletions components/src/ui-style-constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,28 @@ export const warningBg = '#fffcf5'
export const alphaToOpacity35 = '35'

export const backgroundOverlay = `${darkBlack}${alphaToOpacity35}`

// colors liquid
export const electricPurple = '#b925ff'
export const goldenYellow = '#ffd600'
export const aquamarine = '#9dffd8'
export const orangePeel = '#ff9900'
export const skyBlue = '#50d5ff'
export const popPink = '#ff80f5'
export const richBlue = '#0380fb'
export const springGreen = '#7eff42'
export const tartRed = '#ff4f4f'
export const whaleGrey = '#9395a0'

export const liquidColors = [
electricPurple,
goldenYellow,
aquamarine,
orangePeel,
skyBlue,
popPink,
richBlue,
springGreen,
tartRed,
whaleGrey,
]
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@types/lodash": "^4.14.168",
"@types/multer": "^1.4.5",
"@types/netmask": "^1.0.30",
"@types/react-color": "^3.0.6",
"@types/react-redux": "^7.1.16",
"@types/react-test-renderer": "^17.0.1",
"@types/redux-mock-store": "^1.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
"0": {
"displayName": "Water",
"description": "",
"displayColor": "#00d781"
"displayColor": "#b925ff"
}
},
"labwareDefinitions": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@
"0": {
"displayName": "Water",
"description": "",
"displayColor": "#00d781"
"displayColor": "#b925ff"
}
},
"labwareDefinitions": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@
"0": {
"displayName": "samples",
"description": "",
"displayColor": "#00d781"
"displayColor": "#b925ff"
},
"1": { "displayName": "dna", "description": "", "displayColor": "#0076ff" }
"1": { "displayName": "dna", "description": "", "displayColor": "#ffd600" }
},
"labwareDefinitions": {
"opentrons/opentrons_96_tiprack_10ul/1": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,7 @@
"0": {
"displayName": "Liquid",
"description": "",
"displayColor": "#00d781"
"displayColor": "#b925ff"
}
},
"labwareDefinitions": {
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"lodash": "4.17.15",
"query-string": "6.2.0",
"react": "17.0.1",
"react-color": "2.19.3",
"react-dnd": "6.0.0",
"react-dnd-mouse-backend": "0.1.2",
"react-dom": "17.0.1",
Expand Down
29 changes: 29 additions & 0 deletions protocol-designer/src/components/ColorPicker/ColorPicker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.swatch {
display: inline-block;
cursor: pointer;
}

.swatch:hover,
.swatch_enabled {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.41);
}

.color {
width: 59px;
height: 24px;
border-radius: 2px;
}

.popover {
margin-top: 15px;
position: absolute;
right: 24px;
}

.cover {
position: fixed;
top: 10px;
right: 0;
bottom: 0;
left: 0;
}
50 changes: 50 additions & 0 deletions protocol-designer/src/components/ColorPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react'
import cx from 'classnames'
import { ColorResult, TwitterPicker } from 'react-color'
import { COLORS } from '@opentrons/components'

import styles from './ColorPicker.css'

interface ColorPickerProps {
value: string
onChange: (hex: ColorResult['hex']) => void
}

export function ColorPicker(props: ColorPickerProps): JSX.Element {
const [showColorPicker, setShowColorPicker] = React.useState<boolean>(false)

return (
<div>
<div
className={cx(styles.swatch, {
[styles.swatch_enabled]: showColorPicker,
})}
onClick={() => setShowColorPicker(showColorPicker => !showColorPicker)}
>
<div
className={styles.color}
style={{
backgroundColor: props.value,
}}
/>
</div>
{showColorPicker ? (
<div className={styles.popover}>
<div
className={styles.cover}
onClick={() => setShowColorPicker(false)}
/>
<TwitterPicker
colors={COLORS.liquidColors}
color={props.value}
onChange={(color, event) => {
props.onChange(color.hex)
setShowColorPicker(showColorPicker => !showColorPicker)
}}
triangle="top-right"
/>
</div>
) : null}
</div>
)
}
8 changes: 7 additions & 1 deletion protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import { connect } from 'react-redux'
import { LabwareRender, WellGroup } from '@opentrons/components'

import { selectors } from '../../labware-ingred/selectors'
import * as wellContentsSelectors from '../../top-selectors/well-contents'
import * as highlightSelectors from '../../top-selectors/substep-highlight'
import * as tipContentsSelectors from '../../top-selectors/tip-contents'
Expand All @@ -19,6 +20,7 @@ interface OP {

interface SP {
wellContents: ContentsByWell
liquidDisplayColors: string[]
missingTips?: WellGroup | null
highlightedWells?: WellGroup | null
}
Expand All @@ -32,7 +34,10 @@ const LabwareOnDeckComponent = (props: Props): JSX.Element => (
>
<LabwareRender
definition={props.labwareOnDeck.def}
wellFill={wellFillFromWellContents(props.wellContents)}
wellFill={wellFillFromWellContents(
props.wellContents,
props.liquidDisplayColors
)}
highlightedWells={props.highlightedWells}
missingTips={props.missingTips}
/>
Expand Down Expand Up @@ -60,6 +65,7 @@ const mapStateToProps = (state: BaseState, ownProps: OP): SP => {
missingTips: missingTipsByLabwareId
? missingTipsByLabwareId[labwareOnDeck.id]
: null,
liquidDisplayColors: selectors.getLiquidDisplayColors(state),
}
}

Expand Down
11 changes: 9 additions & 2 deletions protocol-designer/src/components/IngredientsList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// TODO: Ian 2018-10-09 figure out what belongs in LiquidsSidebar vs IngredientsList after #2427
import * as React from 'react'

import { useSelector } from 'react-redux'
import { selectors } from '../../labware-ingred/selectors'
import { IconButton, SidePanel } from '@opentrons/components'
import { sortWells } from '@opentrons/shared-data'
import { i18n } from '../../localization'
Expand Down Expand Up @@ -48,6 +49,8 @@ const LiquidGroupCard = (props: LiquidGroupCardProps): JSX.Element | null => {
.sort(sortWells)
.filter(well => labwareWellContents[well][groupId])

const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors)

if (wellsWithIngred.length < 1) {
// do not show liquid card if it has no instances for this labware
return null
Expand All @@ -56,7 +59,11 @@ const LiquidGroupCard = (props: LiquidGroupCardProps): JSX.Element | null => {
return (
<PDTitledList
title={ingredGroup.name || i18n.t('card.unnamedLiquid')}
iconProps={{ style: { fill: swatchColors(groupId) } }}
iconProps={{
style: {
fill: liquidDisplayColors[Number(groupId)] ?? swatchColors(groupId),
},
}}
iconName="circle"
onCollapseToggle={toggleAccordion}
collapsed={!expanded}
Expand Down
10 changes: 8 additions & 2 deletions protocol-designer/src/components/LiquidPlacementModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface SP {
wellContents: ContentsByWell
labwareDef?: LabwareDefinition2 | null
liquidNamesById: WellIngredientNames
liquidDisplayColors: string[]
}

interface DP {
Expand All @@ -55,7 +56,7 @@ class LiquidPlacementModalComponent extends React.Component<Props, State> {
}

render(): JSX.Element {
const { labwareDef, selectedWells } = this.props
const { labwareDef, selectedWells, liquidDisplayColors } = this.props

return (
<div
Expand All @@ -72,7 +73,10 @@ class LiquidPlacementModalComponent extends React.Component<Props, State> {
wellLabelOption: WELL_LABEL_OPTIONS.SHOW_LABEL_INSIDE,
definition: labwareDef,
highlightedWells: this.state.highlightedWells,
wellFill: wellFillFromWellContents(this.props.wellContents),
wellFill: wellFillFromWellContents(
this.props.wellContents,
liquidDisplayColors
),
}}
selectedPrimaryWells={selectedWells}
selectWells={this.props.selectWells}
Expand Down Expand Up @@ -103,6 +107,7 @@ const mapStateToProps = (state: BaseState): SP => {
wellContents: null,
labwareDef: null,
liquidNamesById: {},
liquidDisplayColors: [],
}
}

Expand All @@ -119,6 +124,7 @@ const mapStateToProps = (state: BaseState): SP => {
wellContents,
labwareDef,
liquidNamesById: selectors.getLiquidNamesById(state),
liquidDisplayColors: selectors.getLiquidDisplayColors(state),
}
}

Expand Down
31 changes: 27 additions & 4 deletions protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react'
import { Formik, FormikProps } from 'formik'
import { useSelector } from 'react-redux'
import { Field, Formik, FormikProps } from 'formik'
import * as Yup from 'yup'
import { i18n } from '../../localization'
import { swatchColors } from '../swatchColors'
import {
Card,
CheckboxField,
Expand All @@ -10,10 +12,13 @@ import {
OutlineButton,
PrimaryButton,
} from '@opentrons/components'
import { selectors } from '../../labware-ingred/selectors'
import styles from './LiquidEditForm.css'
import formStyles from '../forms/forms.css'

import { LiquidGroup } from '../../labware-ingred/types'
import { ColorPicker } from '../ColorPicker'
import { ColorResult } from 'react-color'

type Props = LiquidGroup & {
canDelete: boolean
Expand All @@ -24,6 +29,7 @@ type Props = LiquidGroup & {

interface LiquidEditFormValues {
name: string
displayColor: string
description?: string | null
serialize?: boolean
[key: string]: unknown
Expand All @@ -37,15 +43,20 @@ export const liquidEditFormSchema: Yup.Schema<
name: i18n.t('form.liquid_edit.name'),
})
),
displayColor: Yup.string(),
description: Yup.string(),
serialize: Yup.boolean(),
})

export function LiquidEditForm(props: Props): JSX.Element {
const { deleteLiquidGroup, cancelForm, canDelete, saveForm } = props
const selectedLiquid = useSelector(selectors.getSelectedLiquidGroupState)
const nextGroupId = useSelector(selectors.getNextLiquidGroupId)
const liquidId = selectedLiquid.liquidGroupId ?? nextGroupId

const initialValues: LiquidEditFormValues = {
name: props.name || '',
displayColor: props.displayColor ?? swatchColors(liquidId),
description: props.description || '',
serialize: props.serialize || false,
}
Expand All @@ -57,6 +68,7 @@ export function LiquidEditForm(props: Props): JSX.Element {
onSubmit={(values: LiquidEditFormValues) => {
saveForm({
name: values.name,
displayColor: values.displayColor,
description: values.description || null,
serialize: values.serialize || false,
})
Expand All @@ -66,6 +78,7 @@ export function LiquidEditForm(props: Props): JSX.Element {
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
dirty,
errors,
isValid,
Expand All @@ -78,10 +91,10 @@ export function LiquidEditForm(props: Props): JSX.Element {
<div className={formStyles.header}>
{i18n.t('form.liquid_edit.details')}
</div>
<div className={formStyles.row_wrapper}>
<div className={formStyles.row_container}>
<FormGroup
label={i18n.t('form.liquid_edit.name')}
className={formStyles.column_1_2}
className={formStyles.column}
>
<InputField
name="name"
Expand All @@ -93,14 +106,24 @@ export function LiquidEditForm(props: Props): JSX.Element {
</FormGroup>
<FormGroup
label={i18n.t('form.liquid_edit.description')}
className={formStyles.column_1_2}
className={formStyles.column}
>
<InputField
name="description"
value={values.description}
onChange={handleChange}
/>
</FormGroup>
<FormGroup label={i18n.t('form.liquid_edit.displayColor')}>
<Field
name="displayColor"
component={ColorPicker}
value={values.displayColor}
onChange={(color: ColorResult['hex']) => {
setFieldValue('displayColor', color)
}}
/>
</FormGroup>
</div>
</section>

Expand Down
2 changes: 2 additions & 0 deletions protocol-designer/src/components/LiquidsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ function mapStateToProps(state: BaseState): SP {
name: selectedIngredFields.name,
// @ts-expect-error(sa, 2021-6-22): description might not exist
description: selectedIngredFields.description,
// @ts-expect-error(sh, 2022-6-28): displayColor might not exist
displayColor: selectedIngredFields.displayColor,
// @ts-expect-error(sa, 2021-6-22): serialize might not exist
serialize: selectedIngredFields.serialize,
}
Expand Down
Loading

0 comments on commit b4a11af

Please sign in to comment.