From b4a11af69a25d7e481b30c9fc0b4f6fd7c008bc0 Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Mon, 11 Jul 2022 14:49:40 -0400 Subject: [PATCH] feat(protocol-designer): add custom liquid color picker (#10958) * 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 --- components/src/ui-style-constants/colors.ts | 25 ++++++++ package.json | 1 + .../protocol/6/doItAllV3MigratedToV6.json | 2 +- .../protocol/6/doItAllV4MigratedToV6.json | 2 +- .../6/example_1_1_0MigratedFromV1_0_0.json | 4 +- ...andfatheredProtocolMigratedFromV1_0_0.json | 2 +- protocol-designer/package.json | 1 + .../components/ColorPicker/ColorPicker.css | 29 +++++++++ .../src/components/ColorPicker/index.tsx | 50 +++++++++++++++ .../components/DeckSetup/LabwareOnDeck.tsx | 8 ++- .../src/components/IngredientsList/index.tsx | 11 +++- .../src/components/LiquidPlacementModal.tsx | 10 ++- .../components/LiquidsPage/LiquidEditForm.tsx | 31 +++++++-- .../src/components/LiquidsPage/index.tsx | 2 + .../src/components/LiquidsSidebar/index.tsx | 6 +- .../WellSelectionField/WellSelectionModal.tsx | 6 +- .../src/components/forms/forms.css | 11 ++++ .../components/labware/BrowsableLabware.tsx | 5 +- .../src/components/labware/utils.ts | 15 +++-- .../src/components/steplist/IngredPill.tsx | 7 ++- .../src/components/steplist/SubstepRow.tsx | 9 ++- .../src/components/swatchColors.ts | 16 +---- .../src/feature-flags/reducers.ts | 2 + .../src/feature-flags/selectors.ts | 5 ++ protocol-designer/src/feature-flags/types.ts | 1 + .../src/file-data/selectors/fileCreator.ts | 4 +- .../__tests__/ingredients.test.ts | 2 + .../src/labware-ingred/selectors.ts | 16 ++++- protocol-designer/src/labware-ingred/types.ts | 2 + .../src/localization/en/feature_flags.json | 4 ++ .../src/localization/en/form.json | 1 + yarn.lock | 63 ++++++++++++++++++- 32 files changed, 309 insertions(+), 44 deletions(-) create mode 100644 protocol-designer/src/components/ColorPicker/ColorPicker.css create mode 100644 protocol-designer/src/components/ColorPicker/index.tsx diff --git a/components/src/ui-style-constants/colors.ts b/components/src/ui-style-constants/colors.ts index d7ec7907569..07480ff1c85 100644 --- a/components/src/ui-style-constants/colors.ts +++ b/components/src/ui-style-constants/colors.ts @@ -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, +] diff --git a/package.json b/package.json index b81e56935f3..94b7834ad36 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/protocol-designer/fixtures/protocol/6/doItAllV3MigratedToV6.json b/protocol-designer/fixtures/protocol/6/doItAllV3MigratedToV6.json index a42e689b598..080afd6a566 100644 --- a/protocol-designer/fixtures/protocol/6/doItAllV3MigratedToV6.json +++ b/protocol-designer/fixtures/protocol/6/doItAllV3MigratedToV6.json @@ -201,7 +201,7 @@ "0": { "displayName": "Water", "description": "", - "displayColor": "#00d781" + "displayColor": "#b925ff" } }, "labwareDefinitions": { diff --git a/protocol-designer/fixtures/protocol/6/doItAllV4MigratedToV6.json b/protocol-designer/fixtures/protocol/6/doItAllV4MigratedToV6.json index fd9fbe5b7a5..14ddeff567c 100644 --- a/protocol-designer/fixtures/protocol/6/doItAllV4MigratedToV6.json +++ b/protocol-designer/fixtures/protocol/6/doItAllV4MigratedToV6.json @@ -232,7 +232,7 @@ "0": { "displayName": "Water", "description": "", - "displayColor": "#00d781" + "displayColor": "#b925ff" } }, "labwareDefinitions": { diff --git a/protocol-designer/fixtures/protocol/6/example_1_1_0MigratedFromV1_0_0.json b/protocol-designer/fixtures/protocol/6/example_1_1_0MigratedFromV1_0_0.json index 210ea8c6277..99905bc4224 100644 --- a/protocol-designer/fixtures/protocol/6/example_1_1_0MigratedFromV1_0_0.json +++ b/protocol-designer/fixtures/protocol/6/example_1_1_0MigratedFromV1_0_0.json @@ -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": { diff --git a/protocol-designer/fixtures/protocol/6/preFlexGrandfatheredProtocolMigratedFromV1_0_0.json b/protocol-designer/fixtures/protocol/6/preFlexGrandfatheredProtocolMigratedFromV1_0_0.json index a0266fde232..8e2fa4e3f68 100644 --- a/protocol-designer/fixtures/protocol/6/preFlexGrandfatheredProtocolMigratedFromV1_0_0.json +++ b/protocol-designer/fixtures/protocol/6/preFlexGrandfatheredProtocolMigratedFromV1_0_0.json @@ -1826,7 +1826,7 @@ "0": { "displayName": "Liquid", "description": "", - "displayColor": "#00d781" + "displayColor": "#b925ff" } }, "labwareDefinitions": { diff --git a/protocol-designer/package.json b/protocol-designer/package.json index 0adda883509..0a1e5feea33 100755 --- a/protocol-designer/package.json +++ b/protocol-designer/package.json @@ -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", diff --git a/protocol-designer/src/components/ColorPicker/ColorPicker.css b/protocol-designer/src/components/ColorPicker/ColorPicker.css new file mode 100644 index 00000000000..61e30efd289 --- /dev/null +++ b/protocol-designer/src/components/ColorPicker/ColorPicker.css @@ -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; +} diff --git a/protocol-designer/src/components/ColorPicker/index.tsx b/protocol-designer/src/components/ColorPicker/index.tsx new file mode 100644 index 00000000000..99b470ce2a8 --- /dev/null +++ b/protocol-designer/src/components/ColorPicker/index.tsx @@ -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(false) + + return ( +
+
setShowColorPicker(showColorPicker => !showColorPicker)} + > +
+
+ {showColorPicker ? ( +
+
setShowColorPicker(false)} + /> + { + props.onChange(color.hex) + setShowColorPicker(showColorPicker => !showColorPicker) + }} + triangle="top-right" + /> +
+ ) : null} +
+ ) +} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx b/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx index e9c8eae01ac..390346793b4 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx @@ -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' @@ -19,6 +20,7 @@ interface OP { interface SP { wellContents: ContentsByWell + liquidDisplayColors: string[] missingTips?: WellGroup | null highlightedWells?: WellGroup | null } @@ -32,7 +34,10 @@ const LabwareOnDeckComponent = (props: Props): JSX.Element => ( > @@ -60,6 +65,7 @@ const mapStateToProps = (state: BaseState, ownProps: OP): SP => { missingTips: missingTipsByLabwareId ? missingTipsByLabwareId[labwareOnDeck.id] : null, + liquidDisplayColors: selectors.getLiquidDisplayColors(state), } } diff --git a/protocol-designer/src/components/IngredientsList/index.tsx b/protocol-designer/src/components/IngredientsList/index.tsx index 61e30b02f35..bf6fb583a37 100644 --- a/protocol-designer/src/components/IngredientsList/index.tsx +++ b/protocol-designer/src/components/IngredientsList/index.tsx @@ -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' @@ -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 @@ -56,7 +59,11 @@ const LiquidGroupCard = (props: LiquidGroupCardProps): JSX.Element | null => { return ( { } render(): JSX.Element { - const { labwareDef, selectedWells } = this.props + const { labwareDef, selectedWells, liquidDisplayColors } = this.props return (
{ 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} @@ -103,6 +107,7 @@ const mapStateToProps = (state: BaseState): SP => { wellContents: null, labwareDef: null, liquidNamesById: {}, + liquidDisplayColors: [], } } @@ -119,6 +124,7 @@ const mapStateToProps = (state: BaseState): SP => { wellContents, labwareDef, liquidNamesById: selectors.getLiquidNamesById(state), + liquidDisplayColors: selectors.getLiquidDisplayColors(state), } } diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx index e515dbc62b8..e301685b51c 100644 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx @@ -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, @@ -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 @@ -24,6 +29,7 @@ type Props = LiquidGroup & { interface LiquidEditFormValues { name: string + displayColor: string description?: string | null serialize?: boolean [key: string]: unknown @@ -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, } @@ -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, }) @@ -66,6 +78,7 @@ export function LiquidEditForm(props: Props): JSX.Element { handleChange, handleBlur, handleSubmit, + setFieldValue, dirty, errors, isValid, @@ -78,10 +91,10 @@ export function LiquidEditForm(props: Props): JSX.Element {
{i18n.t('form.liquid_edit.details')}
-
+
+ + { + setFieldValue('displayColor', color) + }} + /> +
diff --git a/protocol-designer/src/components/LiquidsPage/index.tsx b/protocol-designer/src/components/LiquidsPage/index.tsx index f6a5e4a20ef..0db89d5dc86 100644 --- a/protocol-designer/src/components/LiquidsPage/index.tsx +++ b/protocol-designer/src/components/LiquidsPage/index.tsx @@ -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, } diff --git a/protocol-designer/src/components/LiquidsSidebar/index.tsx b/protocol-designer/src/components/LiquidsSidebar/index.tsx index 83a5e459e5c..65cbef45675 100644 --- a/protocol-designer/src/components/LiquidsSidebar/index.tsx +++ b/protocol-designer/src/components/LiquidsSidebar/index.tsx @@ -27,13 +27,15 @@ function LiquidsSidebarComponent(props: Props): JSX.Element { const { liquids, selectedLiquid, createNewLiquid, selectLiquid } = props return ( - {liquids.map(({ ingredientId, name }) => ( + {liquids.map(({ ingredientId, name, displayColor }) => ( selectLiquid(ingredientId)} iconName="circle" - iconProps={{ style: { fill: swatchColors(ingredientId) } }} + iconProps={{ + style: { fill: displayColor ?? swatchColors(ingredientId) }, + }} title={name || `Unnamed Ingredient ${ingredientId}`} // fallback, should not happen /> ))} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx b/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx index 9ee1c209307..74ccc23e29d 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx @@ -70,6 +70,7 @@ const WellSelectionModalComponent = ( wellContents, updateHighlightedWells, } = props + const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) return ( { +const ingredIdsToColor = ( + groupIds: string[], + displayColors: string[] +): string | null | undefined => { const filteredIngredIds = groupIds.filter(id => id !== AIR) if (filteredIngredIds.length === 0) return null if (filteredIngredIds.length === 1) { - return swatchColors(filteredIngredIds[0]) + return ( + displayColors[Number(filteredIngredIds[0])] ?? + swatchColors(filteredIngredIds[0]) + ) } return MIXED_WELL_COLOR } export const wellFillFromWellContents = ( - wellContents: ContentsByWell + wellContents: ContentsByWell, + displayColors: string[] ): WellFill => reduce( wellContents, (acc: WellFill, wellContents: WellContents, wellName: string) => { - const wellFill = ingredIdsToColor(wellContents.groupIds) + const wellFill = ingredIdsToColor(wellContents.groupIds, displayColors) return wellFill ? { ...acc, [wellName]: wellFill } : acc }, {} diff --git a/protocol-designer/src/components/steplist/IngredPill.tsx b/protocol-designer/src/components/steplist/IngredPill.tsx index 887b47e1d25..6ed6ca255c5 100644 --- a/protocol-designer/src/components/steplist/IngredPill.tsx +++ b/protocol-designer/src/components/steplist/IngredPill.tsx @@ -1,5 +1,7 @@ import * as React from 'react' +import { useSelector } from 'react-redux' import { Pill, UseHoverTooltipTargetProps } from '@opentrons/components' +import { selectors } from '../../labware-ingred/selectors' import { AIR } from '@opentrons/step-generation' import { swatchColors, MIXED_WELL_COLOR } from '../swatchColors' import { WellIngredientVolumeData, WellIngredientNames } from '../../steplist' @@ -14,6 +16,7 @@ interface Props { export function IngredPill(props: Props): JSX.Element { const { ingredNames, targetProps } = props const ingredIds: string[] = Object.keys(props.ingreds) + const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) if (ingredIds.filter(id => id !== AIR).length === 0) { // Invisible Pill, but has correct height/margin/etc for spacing @@ -21,7 +24,9 @@ export function IngredPill(props: Props): JSX.Element { } const color = - ingredIds.length === 1 ? swatchColors(ingredIds[0]) : MIXED_WELL_COLOR + ingredIds.length === 1 + ? liquidDisplayColors[Number(ingredIds[0])] ?? swatchColors(ingredIds[0]) + : MIXED_WELL_COLOR return ( { + const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) const totalLiquidVolume = reduce( props.ingreds, // @ts-expect-error(sa, 2021-6-20): TODO IMMEDIATELY, this could either be single channel OR multi channel volume data @@ -53,7 +56,11 @@ export const PillTooltipContents = (
diff --git a/protocol-designer/src/components/swatchColors.ts b/protocol-designer/src/components/swatchColors.ts index 4791782055f..eb1945cf129 100644 --- a/protocol-designer/src/components/swatchColors.ts +++ b/protocol-designer/src/components/swatchColors.ts @@ -1,19 +1,9 @@ import { AIR } from '@opentrons/step-generation' -export const MIXED_WELL_COLOR = '#9b9b9b' // NOTE: matches `--c-med-gray` in colors.css +import { COLORS } from '@opentrons/components' +export const MIXED_WELL_COLOR = '#9b9b9b' // NOTE: matches `--c-med-gray` in COLORS.liquidColors.css -// TODO factor into CSS or constants or elsewhere export const swatchColors = (ingredGroupId: string): string => { const num = Number(ingredGroupId) - const colors = [ - '#00d781', - '#0076ff', - '#ff4888', - '#7b21d6', - '#ff6161', - '#065596', - '#2a97dc', - '#d24193', - ] if (!Number.isInteger(num)) { if (ingredGroupId !== AIR) { @@ -25,5 +15,5 @@ export const swatchColors = (ingredGroupId: string): string => { return 'transparent' } - return colors[num % colors.length] + return COLORS.liquidColors[num % COLORS.liquidColors.length] } diff --git a/protocol-designer/src/feature-flags/reducers.ts b/protocol-designer/src/feature-flags/reducers.ts index c1c272c1ad4..956707ef643 100644 --- a/protocol-designer/src/feature-flags/reducers.ts +++ b/protocol-designer/src/feature-flags/reducers.ts @@ -21,6 +21,8 @@ const initialFlags: Flags = { process.env.OT_PD_DISABLE_MODULE_RESTRICTIONS === '1' || false, OT_PD_ENABLE_HEATER_SHAKER: process.env.OT_PD_ENABLE_HEATER_SHAKER === '1' || false, + OT_PD_ENABLE_LIQUID_COLOR_ENHANCEMENTS: + process.env.OT_PD_ENABLE_LIQUID_COLOR_ENHANCEMENTS === '1' || false, } // @ts-expect-error(sa, 2021-6-10): cannot use string literals as action type // TODO IMMEDIATELY: refactor this to the old fashioned way if we cannot have type safety: https://github.com/redux-utilities/redux-actions/issues/282#issuecomment-595163081 diff --git a/protocol-designer/src/feature-flags/selectors.ts b/protocol-designer/src/feature-flags/selectors.ts index eca8972d4a5..468be998a0b 100644 --- a/protocol-designer/src/feature-flags/selectors.ts +++ b/protocol-designer/src/feature-flags/selectors.ts @@ -20,3 +20,8 @@ export const getEnabledHeaterShaker: Selector = createSelector( getFeatureFlagData, flags => flags.OT_PD_ENABLE_HEATER_SHAKER ?? false ) + +export const getEnabledLiquidColorEnhancements: Selector = createSelector( + getFeatureFlagData, + flags => flags.OT_PD_ENABLE_LIQUID_COLOR_ENHANCEMENTS ?? false +) diff --git a/protocol-designer/src/feature-flags/types.ts b/protocol-designer/src/feature-flags/types.ts index 2e80ebe91ec..75d15ddc55f 100644 --- a/protocol-designer/src/feature-flags/types.ts +++ b/protocol-designer/src/feature-flags/types.ts @@ -22,6 +22,7 @@ export type FlagTypes = | 'PRERELEASE_MODE' | 'OT_PD_DISABLE_MODULE_RESTRICTIONS' | 'OT_PD_ENABLE_HEATER_SHAKER' + | 'OT_PD_ENABLE_LIQUID_COLOR_ENHANCEMENTS' // flags that are not in this list only show in prerelease mode export const userFacingFlags: FlagTypes[] = [ 'OT_PD_DISABLE_MODULE_RESTRICTIONS', diff --git a/protocol-designer/src/file-data/selectors/fileCreator.ts b/protocol-designer/src/file-data/selectors/fileCreator.ts index 9003fb9f601..b1cefd6ab67 100644 --- a/protocol-designer/src/file-data/selectors/fileCreator.ts +++ b/protocol-designer/src/file-data/selectors/fileCreator.ts @@ -29,7 +29,6 @@ import { DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP, DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP, } from '../../constants' -import { swatchColors } from '../../components/swatchColors' import type { ModuleEntity, PipetteEntity, @@ -47,6 +46,7 @@ import type { LoadModuleCreateCommand, LoadPipetteCreateCommand, } from '@opentrons/shared-data/protocol/types/schemaV6/command/setup' +import { swatchColors } from '../../components/swatchColors' // TODO: BC: 2018-02-21 uncomment this assert, causes test failures // assert(!isEmpty(process.env.OT_PD_VERSION), 'Could not find application version!') if (isEmpty(process.env.OT_PD_VERSION)) @@ -186,7 +186,7 @@ export const createFile: Selector = createSelector( [liquidId]: { displayName: liquidData.name, description: liquidData.description ?? '', - displayColor: swatchColors(liquidId), + displayColor: liquidData.displayColor ?? swatchColors(liquidId), }, } }, diff --git a/protocol-designer/src/labware-ingred/__tests__/ingredients.test.ts b/protocol-designer/src/labware-ingred/__tests__/ingredients.test.ts index 2181ee86edc..0059a3bc473 100644 --- a/protocol-designer/src/labware-ingred/__tests__/ingredients.test.ts +++ b/protocol-designer/src/labware-ingred/__tests__/ingredients.test.ts @@ -18,6 +18,7 @@ describe('DUPLICATE_LABWARE action', () => { wellDetailsByLocation: null, concentration: '50 mol/ng', description: '', + displayColor: '#b925ff', serialize: false, }, ingred4: { @@ -25,6 +26,7 @@ describe('DUPLICATE_LABWARE action', () => { wellDetailsByLocation: null, concentration: '100%', description: '', + displayColor: '#ffd600', serialize: false, }, } diff --git a/protocol-designer/src/labware-ingred/selectors.ts b/protocol-designer/src/labware-ingred/selectors.ts index feceb5f66fc..8238026c0db 100644 --- a/protocol-designer/src/labware-ingred/selectors.ts +++ b/protocol-designer/src/labware-ingred/selectors.ts @@ -105,12 +105,13 @@ const allIngredientGroupFields: Selector< const allIngredientNamesIds: Selector< RootSlice, OrderedLiquids -> = createSelector(getLiquidGroupsById, ingreds => - Object.keys(ingreds).map(ingredId => ({ +> = createSelector(getLiquidGroupsById, ingreds => { + return Object.keys(ingreds).map(ingredId => ({ ingredientId: ingredId, name: ingreds[ingredId].name, + displayColor: ingreds[ingredId].displayColor, })) -) +}) const getLabwareSelectionMode: Selector = createSelector( rootSelector, rootState => { @@ -147,6 +148,14 @@ const getDeckHasLiquid: Selector = createSelector( getLiquidGroupsOnDeck, liquidGroups => liquidGroups.length > 0 ) +const getLiquidDisplayColors: Selector = createSelector( + getLiquidGroupsById, + liquids => + Object.values(liquids).reduce((acc, curr) => { + acc.push(curr.displayColor) + return acc + }, []) +) // TODO: prune selectors export const selectors = { rootSelector, @@ -166,4 +175,5 @@ export const selectors = { allIngredientNamesIds, selectedAddLabwareSlot, getDeckHasLiquid, + getLiquidDisplayColors, } diff --git a/protocol-designer/src/labware-ingred/types.ts b/protocol-designer/src/labware-ingred/types.ts index 92dd0dba100..f5997158e32 100644 --- a/protocol-designer/src/labware-ingred/types.ts +++ b/protocol-designer/src/labware-ingred/types.ts @@ -28,11 +28,13 @@ export interface WellContentsByLabware { export type OrderedLiquids = Array<{ ingredientId: string name: string | null | undefined + displayColor: string | null | undefined }> // TODO: Ian 2018-10-15 audit & rename these confusing types export interface LiquidGroup { name: string | null | undefined description: string | null | undefined + displayColor: string serialize: boolean } export type IngredInputs = LiquidGroup & { diff --git a/protocol-designer/src/localization/en/feature_flags.json b/protocol-designer/src/localization/en/feature_flags.json index 6a3b56bab1d..34ec8149f61 100644 --- a/protocol-designer/src/localization/en/feature_flags.json +++ b/protocol-designer/src/localization/en/feature_flags.json @@ -19,5 +19,9 @@ "OT_PD_ENABLE_SCHEMA_V6": { "title": "Enable schema v6 support", "description": "Allow users to migrate older protocols to schema v6" + }, + "OT_PD_ENABLE_LIQUID_COLOR_ENHANCEMENTS": { + "title": "Enable liquid color enhancements", + "description": "Allow users to enable liquid color picking" } } diff --git a/protocol-designer/src/localization/en/form.json b/protocol-designer/src/localization/en/form.json index 8549d16ec49..694756b34db 100644 --- a/protocol-designer/src/localization/en/form.json +++ b/protocol-designer/src/localization/en/form.json @@ -15,6 +15,7 @@ "liquid_edit": { "details": "Details", "description": "Description", + "displayColor": "Color", "name": "Liquid name", "serialize": "Serialize", "serialize_title": "Serialization", diff --git a/yarn.lock b/yarn.lock index 999a8f1f5be..b64868d5ecb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1556,6 +1556,11 @@ object-assign "^4.1.1" scheduler "^0.20.1" +"@icons/material@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" + integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -4060,6 +4065,14 @@ dependencies: "@types/react" "*" +"@types/react-color@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.6.tgz#602fed023802b2424e7cd6ff3594ccd3d5055f9a" + integrity sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w== + dependencies: + "@types/react" "*" + "@types/reactcss" "*" + "@types/react-dom@*", "@types/react-dom@>=16.9.0": version "17.0.7" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.7.tgz#b8ee15ead9e5d6c2c858b44949fdf2ebe5212232" @@ -4143,6 +4156,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/reactcss@*": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.6.tgz#133c1e7e896f2726370d1d5a26bf06a30a038bcc" + integrity sha512-qaIzpCuXNWomGR1Xq8SCFTtF4v8V27Y6f+b9+bzHiv087MylI/nTCqqdChNeWS7tslgROmYB7yeiruWX7WnqNg== + dependencies: + "@types/react" "*" + "@types/redux-actions@2.6.1": version "2.6.1" resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.1.tgz#0940e97fa35ad3004316bddb391d8e01d2efa605" @@ -14111,7 +14131,7 @@ lodash@4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.0: +lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -14450,6 +14470,11 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +material-colors@^1.2.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" + integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== + mathml-tag-names@^2.1.0: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" @@ -17582,6 +17607,15 @@ prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" +prop-types@^15.5.10: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + property-expr@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" @@ -17912,6 +17946,19 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-color@2.19.3: + version "2.19.3" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d" + integrity sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA== + dependencies: + "@icons/material" "^0.2.4" + lodash "^4.17.15" + lodash-es "^4.17.15" + material-colors "^1.2.1" + prop-types "^15.5.10" + reactcss "^1.2.0" + tinycolor2 "^1.4.1" + react-colorful@^5.0.1: version "5.2.2" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.2.2.tgz#0a69d0648db47e51359d343854d83d250a742243" @@ -18097,7 +18144,7 @@ react-intersection-observer@^8.33.1: resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.33.1.tgz#8e6442cac7052ed63056e191b7539e423e7d5c64" integrity sha512-3v+qaJvp3D1MlGHyM+KISVg/CMhPiOlO6FgPHcluqHkx4YFCLuyXNlQ/LE6UkbODXlQcLOppfX6UMxCEkUhDLw== -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -18334,6 +18381,13 @@ react@17.0.1: loose-envify "^1.1.0" object-assign "^4.1.1" +reactcss@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" + integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A== + dependencies: + lodash "^4.0.1" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -21165,6 +21219,11 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tinycolor2@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" + integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== + tmp-promise@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.2.tgz#6e933782abff8b00c3119d63589ca1fb9caaa62a"