Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol-designer): add custom liquid color picker #10958

Merged
merged 16 commits into from
Jul 11, 2022
Merged
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>
)
}
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