From 6a0ad8a1d5f474f2573f77041a21fc051f50dfa6 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Wed, 10 Oct 2018 15:22:54 -0400 Subject: [PATCH 1/7] feat(protocol-designer): add liquid page edit form Closes #2427 --- .../components/LiquidsPage/LiquidEditForm.css | 26 +++++++++ .../components/LiquidsPage/LiquidEditForm.js | 53 +++++++++++++++++++ .../src/components/LiquidsPage/index.js | 3 +- .../src/components/StepEditForm/ButtonRow.js | 3 +- .../components/StepEditForm/StepEditForm.css | 9 ---- protocol-designer/src/components/forms.css | 6 +++ 6 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 protocol-designer/src/components/LiquidsPage/LiquidEditForm.css create mode 100644 protocol-designer/src/components/LiquidsPage/LiquidEditForm.js diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.css b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.css new file mode 100644 index 00000000000..31b94204d90 --- /dev/null +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.css @@ -0,0 +1,26 @@ +@import '@opentrons/components'; + +.form_card { + margin: 1rem; + padding: 1rem; +} + +.section { + padding-bottom: 2rem; +} + +.info_text { + padding-bottom: 1.5rem; +} + +.button_row { + lost-utility: clearfix; + + & > * { + lost-column: 1/6; + } + + & > *:nth-child(2) { + lost-offset: 3/6; + } +} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js new file mode 100644 index 00000000000..0f86273ebfd --- /dev/null +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js @@ -0,0 +1,53 @@ +// @flow +import * as React from 'react' +import {connect} from 'react-redux' + +import { + Card, + CheckboxField, + FormGroup, + InputField, + OutlineButton, + PrimaryButton, +} from '@opentrons/components' +import styles from './LiquidEditForm.css' +import formStyles from '../forms.css' + +type Props = { + // TODO +} + +// TODO IMMEDIATELY: internationalization of copy +class LiquidEditForm extends React.Component { + render () { + return ( + +
+
Details
+
+ + + + + + +
+
+ +
+
Serialization
+

{'Each placement of the liquid will get its own number. ("Sample 1", "Sample 2", "Sample 3")'}

+ console.log('TODO IMMEDIATELY')} /> +
+ +
+ DELETE + CANCEL + SAVE +
+
+ ) + } +} + +export default connect()(LiquidEditForm) diff --git a/protocol-designer/src/components/LiquidsPage/index.js b/protocol-designer/src/components/LiquidsPage/index.js index 1a144336030..425cd61c087 100644 --- a/protocol-designer/src/components/LiquidsPage/index.js +++ b/protocol-designer/src/components/LiquidsPage/index.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react' +import LiquidEditForm from './LiquidEditForm' export default function LiquidsPage () { - return
TODO! This page will be implemented in the next ticket (#2427)
+ return } diff --git a/protocol-designer/src/components/StepEditForm/ButtonRow.js b/protocol-designer/src/components/StepEditForm/ButtonRow.js index 6160fd1fbd3..8fbf8c3bff8 100644 --- a/protocol-designer/src/components/StepEditForm/ButtonRow.js +++ b/protocol-designer/src/components/StepEditForm/ButtonRow.js @@ -6,6 +6,7 @@ import {OutlineButton, PrimaryButton} from '@opentrons/components' import {actions, selectors} from '../../steplist' import type {BaseState, ThunkDispatch} from '../../types' import styles from './StepEditForm.css' +import formStyles from '../Form.css' type OP = {onDelete?: (event: SyntheticEvent<>) => mixed} type SP = {canSave?: ?boolean} @@ -19,7 +20,7 @@ type Props = OP & SP & DP const ButtonRow = (props: Props) => { const {canSave, onDelete, onSave, onCancel, onClickMoreOptions} = props return ( -
+
DELETE NOTES CANCEL diff --git a/protocol-designer/src/components/StepEditForm/StepEditForm.css b/protocol-designer/src/components/StepEditForm/StepEditForm.css index 8b2fdebd199..657e3a9bce6 100644 --- a/protocol-designer/src/components/StepEditForm/StepEditForm.css +++ b/protocol-designer/src/components/StepEditForm/StepEditForm.css @@ -90,15 +90,6 @@ /* form buttons */ -.button_row { - lost-utility: clearfix; - margin-top: 2rem; - - & > * { - lost-column: 1/6; - } -} - .cancel_button { lost-offset: 2/6; } diff --git a/protocol-designer/src/components/forms.css b/protocol-designer/src/components/forms.css index 679a989730b..af8dc0bcf58 100644 --- a/protocol-designer/src/components/forms.css +++ b/protocol-designer/src/components/forms.css @@ -9,6 +9,12 @@ box-shadow: 0 0.25rem 1rem 0 rgba(0, 0, 0, 0.25); } +.header { + @apply --font-header-dark; + + padding-bottom: 1rem; +} + /* NOTE: explicit column classes should only be used for forms with a layout * that doesn't match the layout of transfer/distribute/consolidate/mix. * Eg the Pause form is an example of an exceptional layout. From f831b26f259a41bdb5de446b7cacf0ca4b02dc66 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Fri, 12 Oct 2018 17:15:07 -0400 Subject: [PATCH 2/7] feat(protocol-designer): implement liquids page interactivity * liquids form & sidebar interactivity Closes #2427 --- .../components/IngredientPropertiesForm.css | 2 +- .../components/IngredientPropertiesForm.js | 14 +-- .../src/components/IngredientsList.js | 4 +- .../components/LiquidsPage/LiquidEditForm.js | 102 ++++++++++++++++-- .../src/components/LiquidsSidebar/index.js | 29 +++-- .../src/components/StepCreationButton.js | 4 +- .../src/components/WellToolTip.js | 4 +- ...StepCreationButton.css => listButtons.css} | 2 +- .../__tests__/ingredients.test.js | 10 +- .../__tests__/selectors.test.js | 2 +- .../src/labware-ingred/actions.js | 53 +++++++-- .../src/labware-ingred/reducers/index.js | 49 ++++++++- protocol-designer/src/labware-ingred/types.js | 10 +- .../__tests__/wellContentsAllLabware.test.js | 2 +- 14 files changed, 236 insertions(+), 51 deletions(-) rename protocol-designer/src/components/{StepCreationButton.css => listButtons.css} (85%) diff --git a/protocol-designer/src/components/IngredientPropertiesForm.css b/protocol-designer/src/components/IngredientPropertiesForm.css index b9839a95144..8456bb24802 100644 --- a/protocol-designer/src/components/IngredientPropertiesForm.css +++ b/protocol-designer/src/components/IngredientPropertiesForm.css @@ -18,7 +18,7 @@ lost-column: 4/16; } -.individualize { +.serialize { lost-column: 6/16; } diff --git a/protocol-designer/src/components/IngredientPropertiesForm.js b/protocol-designer/src/components/IngredientPropertiesForm.js index 6da9eae778d..303fe98237f 100644 --- a/protocol-designer/src/components/IngredientPropertiesForm.js +++ b/protocol-designer/src/components/IngredientPropertiesForm.js @@ -112,7 +112,7 @@ class IngredientPropertiesForm extends React.Component { name: null, volume: null, description: null, - individualize: false, + serialize: false, }, commonIngredGroupId: null, } @@ -127,7 +127,7 @@ class IngredientPropertiesForm extends React.Component { : this.state.commonIngredGroupId, }) }, - getSubstate: (inputKey) => this.state.input[inputKey], + getSubstate: (inputKey) => this.state.input[inputKey] || null, }) } @@ -143,7 +143,7 @@ class IngredientPropertiesForm extends React.Component { const allIngredientGroupFields = (nextIngredGroupFields || this.props.allIngredientGroupFields || {}) if (ingredGroupId && ingredGroupId in allIngredientGroupFields) { - const {name, volume, description, individualize} = this.state.input + const {name, volume, description, serialize} = this.state.input const newIngredFields = allIngredientGroupFields[ingredGroupId] this.setState({ ...this.state, @@ -151,7 +151,7 @@ class IngredientPropertiesForm extends React.Component { name: newIngredFields.name || name, volume: newIngredFields.volume || volume, description: newIngredFields.description || description, - individualize: newIngredFields.individualize || individualize, + serialize: newIngredFields.serialize || serialize, }, }, cb) } else { @@ -166,7 +166,7 @@ class IngredientPropertiesForm extends React.Component { name: null, volume: null, description: null, - individualize: false, + serialize: false, }, }, cb) } @@ -290,11 +290,11 @@ class IngredientPropertiesForm extends React.Component { diff --git a/protocol-designer/src/components/IngredientsList.js b/protocol-designer/src/components/IngredientsList.js index ba988d65a13..217ae114962 100644 --- a/protocol-designer/src/components/IngredientsList.js +++ b/protocol-designer/src/components/IngredientsList.js @@ -50,7 +50,7 @@ class IngredGroupCard extends React.Component { groupId, labwareWellContents, } = this.props - const {individualize, description, name} = ingredGroup + const {serialize, description, name} = ingredGroup const {isExpanded} = this.state const wellsWithIngred = Object.keys(labwareWellContents) @@ -91,7 +91,7 @@ class IngredGroupCard extends React.Component { } return mixed, + cancelForm: () => mixed, + saveForm: (IngredInputs) => mixed, +} +type State = IngredInputs + +type WrapperProps = {showForm: boolean, formKey: string, formProps: Props} + +type SP = { + ...IngredInputs, + _liquidGroupId: ?string, + showForm: boolean, } // TODO IMMEDIATELY: internationalization of copy -class LiquidEditForm extends React.Component { +class LiquidEditForm extends React.Component { + constructor (props: Props) { + super(props) + this.state = { + name: props.name, + description: props.description, + serialize: props.serialize || false, + } + } + + updateForm = (fieldName: $Keys) => (e: SyntheticInputEvent<*>) => { + // TODO how to handle checkbox cleanly??? + if (fieldName === 'serialize') { + this.setState({[fieldName]: !this.state[fieldName]}) + } else { + this.setState({[fieldName]: e.currentTarget.value}) + } + } + + handleSaveForm = (e: SyntheticMouseEvent<*>) => { + this.props.saveForm(this.state) + } + render () { + const {deleteLiquidGroup, cancelForm} = this.props + const {name, description, serialize} = this.state return (
Details
- + - +
@@ -37,17 +79,59 @@ class LiquidEditForm extends React.Component {
Serialization

{'Each placement of the liquid will get its own number. ("Sample 1", "Sample 2", "Sample 3")'}

- console.log('TODO IMMEDIATELY')} /> +
- DELETE - CANCEL - SAVE + DELETE + CANCEL + SAVE
) } } -export default connect()(LiquidEditForm) +function LiquidEditFormWrapper (props: WrapperProps) { + const {showForm, formKey, formProps} = props + return showForm + ? + : null +} + +function mapStateToProps (state: BaseState): SP { + const selectedLiquidGroupState = labwareIngredSelectors.getSelectedLiquidGroupState(state) + const _liquidGroupId = (selectedLiquidGroupState && selectedLiquidGroupState.liquidGroupId) + const allIngredientGroupFields = labwareIngredSelectors.allIngredientGroupFields(state) + const selectedIngredFields = _liquidGroupId ? allIngredientGroupFields[_liquidGroupId] : {} + const showForm = Boolean(selectedLiquidGroupState.liquidGroupId || selectedLiquidGroupState.newLiquidGroup) + assert(!(_liquidGroupId && !selectedIngredFields), `Expected selected liquid group "${String(_liquidGroupId)}" to have fields in allIngredientGroupFields`) + + return { + _liquidGroupId, + showForm, + name: selectedIngredFields.name, + description: selectedIngredFields.description, + serialize: selectedIngredFields.serialize, + } +} + +function mergeProps (stateProps: SP, dispatchProps: {dispatch: ThunkDispatch<*>}): WrapperProps { + const {dispatch} = dispatchProps + const {showForm, _liquidGroupId, ...passThruFormProps} = stateProps + return { + showForm, + formKey: _liquidGroupId || '__new_form__', + formProps: { + ...passThruFormProps, + deleteLiquidGroup: () => window.alert('Deleting liquids is not yet implemented'), // TODO: Ian 2018-10-12 later ticket + cancelForm: () => dispatch(labwareIngredActions.deselectLiquidGroup()), + saveForm: (formData: IngredInputs) => dispatch(labwareIngredActions.editLiquidGroup({ + ...formData, + liquidGroupId: _liquidGroupId, + })), + }, + } +} + +export default connect(mapStateToProps, null, mergeProps)(LiquidEditFormWrapper) diff --git a/protocol-designer/src/components/LiquidsSidebar/index.js b/protocol-designer/src/components/LiquidsSidebar/index.js index 3cc598e9282..1cff751f5d0 100644 --- a/protocol-designer/src/components/LiquidsSidebar/index.js +++ b/protocol-designer/src/components/LiquidsSidebar/index.js @@ -1,17 +1,24 @@ // @flow import * as React from 'react' import {connect} from 'react-redux' -import {SidePanel, swatchColors} from '@opentrons/components' +import { + PrimaryButton, + SidePanel, + swatchColors, +} from '@opentrons/components' import {PDTitledList} from '../lists' +import listButtonStyles from '../listButtons.css' import {selectors as labwareIngredSelectors} from '../../labware-ingred/reducers' import type {OrderedLiquids} from '../../labware-ingred/types' +import * as labwareIngredActions from '../../labware-ingred/actions' import type {BaseState} from '../../types' type Props = { liquids: OrderedLiquids, selectedLiquid: ?string, - handleClickLiquid: (liquidId: string) => () => mixed, + createNewLiquid: () => mixed, + selectLiquid: (liquidId: string) => mixed, } type SP = { @@ -22,33 +29,43 @@ type SP = { type DP = $Diff function LiquidsSidebar (props: Props) { - const {liquids, selectedLiquid, handleClickLiquid} = props + const {liquids, selectedLiquid, createNewLiquid, selectLiquid} = props return ( {liquids.map(({ingredientId, name}) => ( selectLiquid(ingredientId)} iconName='circle' iconProps={{style: {fill: swatchColors(Number(ingredientId))}}} title={name || `Unnamed Ingredient ${ingredientId}`} // fallback, should not happen /> ))} +
+ + New Liquid + +
) } function mapStateToProps (state: BaseState): SP { + const selectedLiquidGroup = labwareIngredSelectors.getSelectedLiquidGroupState(state) return { liquids: labwareIngredSelectors.allIngredientNamesIds(state), - selectedLiquid: '0', // TODO: Ian 2018-10-09 implement in #2427 + selectedLiquid: selectedLiquidGroup && selectedLiquidGroup.liquidGroupId, } } function mapDispatchToProps (dispatch: Dispatch<*>): DP { return { - handleClickLiquid: (liquidId) => () => console.log('TODO: select liquid', liquidId), // TODO: Ian 2018-10-09 implement in #2427 + selectLiquid: (liquidGroupId) => + dispatch(labwareIngredActions.selectLiquidGroup(liquidGroupId)), + createNewLiquid: () => dispatch(labwareIngredActions.createNewLiquidGroup()), } } diff --git a/protocol-designer/src/components/StepCreationButton.js b/protocol-designer/src/components/StepCreationButton.js index 5d9fd68a9a6..f78767d1fb6 100644 --- a/protocol-designer/src/components/StepCreationButton.js +++ b/protocol-designer/src/components/StepCreationButton.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react' -import styles from './StepCreationButton.css' +import styles from './listButtons.css' import i18n from '../localization' import {HoverTooltip, PrimaryButton} from '@opentrons/components' @@ -38,7 +38,7 @@ function StepCreationButton (props: Props) { ) return ( -
+
diff --git a/protocol-designer/src/components/WellToolTip.js b/protocol-designer/src/components/WellToolTip.js index 67b0e8ed88e..e165bf4511a 100644 --- a/protocol-designer/src/components/WellToolTip.js +++ b/protocol-designer/src/components/WellToolTip.js @@ -7,7 +7,7 @@ type Props = { wellContent: { name: string, volume: number, - individualize: boolean, + serialize: boolean, wellName: string, concentration?: string, @@ -25,7 +25,7 @@ export default function WellToolTip (props: Props) {
{wellContent.wellName}
- {wellContent.individualize &&
+ {wellContent.serialize &&
{wellContent.name || ''} {wellContent.ingredientNum}
}
diff --git a/protocol-designer/src/components/StepCreationButton.css b/protocol-designer/src/components/listButtons.css similarity index 85% rename from protocol-designer/src/components/StepCreationButton.css rename to protocol-designer/src/components/listButtons.css index c09ec01aee0..e66c519f975 100644 --- a/protocol-designer/src/components/StepCreationButton.css +++ b/protocol-designer/src/components/listButtons.css @@ -1,4 +1,4 @@ -.step_creation_button { +.list_item_button { z-index: 5; position: relative; padding: 1rem 2rem 1.25rem 2rem; diff --git a/protocol-designer/src/labware-ingred/__tests__/ingredients.test.js b/protocol-designer/src/labware-ingred/__tests__/ingredients.test.js index ceef52610b9..b32ebbb2d9e 100644 --- a/protocol-designer/src/labware-ingred/__tests__/ingredients.test.js +++ b/protocol-designer/src/labware-ingred/__tests__/ingredients.test.js @@ -32,7 +32,7 @@ describe('DELETE_INGREDIENT action', () => { wellDetailsByLocation: null, concentration: '50 mol/ng', description: '', - individualize: false, + serialize: false, }, '4': 'blah', } @@ -108,14 +108,14 @@ describe.skip('COPY_LABWARE action', () => { wellDetailsByLocation: null, concentration: '50 mol/ng', description: '', - individualize: false, + serialize: false, }, ingred4: { name: 'Other Ingred', wellDetailsByLocation: null, concentration: '100%', description: '', - individualize: false, + serialize: false, }, } @@ -168,7 +168,7 @@ describe('EDIT_INGREDIENT action', () => { name: 'Cool Ingredient', volume: 250, description: 'far out!', - individualize: false, + serialize: false, } const resultingIngred = omit(ingredFields, ['volume']) @@ -212,7 +212,7 @@ describe('EDIT_INGREDIENT action', () => { name: 'Cool Ingredient', volume: 250, description: 'far out!', - individualize: false, + serialize: false, containerId: 'container1Id', groupId: 'newIngredId', diff --git a/protocol-designer/src/labware-ingred/__tests__/selectors.test.js b/protocol-designer/src/labware-ingred/__tests__/selectors.test.js index 53d7990e7d5..d5ff5c55371 100644 --- a/protocol-designer/src/labware-ingred/__tests__/selectors.test.js +++ b/protocol-designer/src/labware-ingred/__tests__/selectors.test.js @@ -6,7 +6,7 @@ const baseIngredFields = { groupId: '0', name: 'Some Ingred', description: null, - individualize: false, + serialize: false, } const allIngredientsXXSingleIngred = { diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index f3408968246..81b047863f9 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -1,7 +1,6 @@ // @flow import {createAction} from 'redux-actions' import type {Dispatch} from 'redux' -import max from 'lodash/max' import {selectors} from './reducers' import wellSelectionSelectors from '../well-selection/selectors' @@ -166,10 +165,10 @@ export type EditIngredientPayload = { groupId: string | null, // null indicates new ingredient is being created } +// TODO: Ian 2018-10-12 this is deprecated, remove when "add liquids to deck" modal is redone export const editIngredient = (payload: EditIngredientPayload) => (dispatch: Dispatch, getState: GetState) => { const state = getState() const container = selectors.getSelectedContainer(state) - const allIngredients = selectors.getIngredientGroups(state) const {groupId, ...inputFields} = payload @@ -190,17 +189,59 @@ export const editIngredient = (payload: EditIngredientPayload) => (dispatch: Dis }) } - // TODO: Ian 2018-02-19 make selector - const nextGroupId: string = ((max(Object.keys(allIngredients).map(id => parseInt(id))) + 1) || 0).toString() - return dispatch({ type: 'EDIT_INGREDIENT', payload: { ...inputFields, // if it matches the name of the clone parent, append "copy" to that name containerId: container.id, - groupId: nextGroupId, + groupId: selectors.getNextLiquidGroupId(state), wells: wellSelectionSelectors.selectedWellNames(state), }, }) } + +export type SelectLiquidAction = { + type: 'SELECT_LIQUID_GROUP', + payload: string, +} + +export function selectLiquidGroup (liquidGroupId: string): SelectLiquidAction { + return { + type: 'SELECT_LIQUID_GROUP', + payload: liquidGroupId, + } +} + +export function deselectLiquidGroup () { + return {type: 'DESELECT_LIQUID_GROUP'} +} + +export function createNewLiquidGroup () { + return {type: 'CREATE_NEW_LIQUID_GROUP_FORM'} +} + +export type EditLiquidGroupAction = { + type: 'EDIT_LIQUID_GROUP', + payload: { + liquidGroupId: string, + ...IngredInputFields, + }, +} + +// NOTE: with no ID, a new one is assigned +export const editLiquidGroup = ( + args: {liquidGroupId: ?string, ...IngredInputFields} +) => (dispatch: Dispatch, getState: GetState +) => { + // TODO: Ian 2018-10-12 flow doesn't understand unpacking in: {...args, liquidGroupId: args.id || 'blahId'} + dispatch({ + type: 'EDIT_LIQUID_GROUP', + payload: { + name: args.name, + serialize: args.serialize, + description: args.description, + liquidGroupId: args.liquidGroupId || selectors.getNextLiquidGroupId(getState()), + }, + }) +} diff --git a/protocol-designer/src/labware-ingred/reducers/index.js b/protocol-designer/src/labware-ingred/reducers/index.js index f8b3f43ec37..d1b68f0a837 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.js +++ b/protocol-designer/src/labware-ingred/reducers/index.js @@ -6,6 +6,7 @@ import {createSelector} from 'reselect' import omit from 'lodash/omit' import mapValues from 'lodash/mapValues' +import max from 'lodash/max' import pickBy from 'lodash/pickBy' import reduce from 'lodash/reduce' import isEmpty from 'lodash/isEmpty' @@ -31,7 +32,13 @@ import * as actions from '../actions' import {getPDMetadata} from '../../file-types' import type {BaseState, Selector, Options} from '../../types' import type {LoadFileAction} from '../../load-file' -import type {MoveLabware, DeleteIngredient, EditIngredient} from '../actions' +import type { + DeleteIngredient, + EditIngredient, + EditLiquidGroupAction, + MoveLabware, + SelectLiquidAction, +} from '../actions' // external actions (for types) import typeof {openWellSelectionModal} from '../../well-selection/actions' @@ -92,6 +99,20 @@ type ContainersState = { [id: string]: ?Labware, } +export type SelectedLiquidGroupState = {liquidGroupId: ?string, newLiquidGroup?: true} +const unselectedLiquidGroupState = {liquidGroupId: null} +// This is only a concern of the liquid page. +// null = nothing selected, newLiquidGroup: true means user is creating new liquid +const selectedLiquidGroup = handleActions({ + SELECT_LIQUID_GROUP: (state: SelectedLiquidGroupState, action: SelectLiquidAction): SelectedLiquidGroupState => + ({liquidGroupId: action.payload}), + DESELECT_LIQUID_GROUP: () => unselectedLiquidGroupState, + CREATE_NEW_LIQUID_GROUP_FORM: (): SelectedLiquidGroupState => + ({liquidGroupId: null, newLiquidGroup: true}), + NAVIGATE_TO_PAGE: () => unselectedLiquidGroupState, // clear selection on navigate + EDIT_LIQUID_GROUP: () => unselectedLiquidGroupState, // clear on form save +}, unselectedLiquidGroupState) + const initialLabwareState: ContainersState = { [FIXED_TRASH_ID]: { id: FIXED_TRASH_ID, @@ -192,11 +213,19 @@ export const savedLabware = handleActions({ type IngredientsState = IngredientGroups export const ingredients = handleActions({ + EDIT_LIQUID_GROUP: (state: IngredientsState, action: EditLiquidGroupAction): IngredientsState => { + const {liquidGroupId} = action.payload + return { + ...state, + [liquidGroupId]: {...state[liquidGroupId], ...action.payload}, + } + }, EDIT_INGREDIENT: (state, action: EditIngredient) => { - const {groupId, description, individualize, name} = action.payload + // TODO: Ian 2018-10-12 this is deprecated, remove when "add liquids to deck" modal is redone + const {groupId, description, serialize, name} = action.payload const ingredFields: IngredientInstance = { description, - individualize, + serialize, name, } @@ -274,6 +303,7 @@ export type RootState = {| drillDownLabwareId: DrillDownLabwareId, containers: ContainersState, savedLabware: SavedLabwareState, + selectedLiquidGroup: SelectedLiquidGroupState, ingredients: IngredientsState, ingredLocations: LocationsState, renameLabwareFormMode: RenameLabwareFormModeState, @@ -284,6 +314,7 @@ const rootReducer = combineReducers({ modeLabwareSelection, moveLabwareMode, selectedContainerId, + selectedLiquidGroup, drillDownLabwareId, containers, savedLabware, @@ -318,6 +349,11 @@ const getLabwareTypes: Selector = createSelector( const getIngredientGroups = (state: BaseState) => rootSelector(state).ingredients const getIngredientLocations = (state: BaseState) => rootSelector(state).ingredLocations +const getNextLiquidGroupId: Selector = createSelector( + getIngredientGroups, + (_ingredGroups) => ((max(Object.keys(_ingredGroups).map(id => parseInt(id))) + 1) || 0).toString() +) + const getIngredientNames: Selector<{[ingredId: string]: string}> = createSelector( getIngredientGroups, ingredGroups => mapValues(ingredGroups, (ingred: IngredientInstance) => ingred.name) @@ -382,6 +418,11 @@ const getSelectedContainerId: Selector = createSelector( rootState => rootState.selectedContainerId ) +const getSelectedLiquidGroupState: Selector = createSelector( + rootSelector, + rootState => rootState.selectedLiquidGroup +) + const getSelectedContainer: Selector = createSelector( getSelectedContainerId, getLabware, @@ -472,9 +513,11 @@ export const selectors = { getLabware, getLabwareNames, getLabwareTypes, + getNextLiquidGroupId, getSavedLabware, getSelectedContainer, getSelectedContainerId, + getSelectedLiquidGroupState, getDrillDownLabwareId, activeModals, diff --git a/protocol-designer/src/labware-ingred/types.js b/protocol-designer/src/labware-ingred/types.js index 4de8f67b6ef..99cea829314 100644 --- a/protocol-designer/src/labware-ingred/types.js +++ b/protocol-designer/src/labware-ingred/types.js @@ -44,11 +44,11 @@ export type OrderedLiquids = Array<{ name: ?string, }> -export type IngredInputs = { - name: string | null, - volume: number | null, - description: string | null, - individualize: boolean, +export type IngredInputs = { // TODO: Ian 2018-10-12 rename to 'LiquidFormFields' + name: ?string, + volume?: ?number, + description: ?string, + serialize: boolean, } export type IngredInputFields = $Exact diff --git a/protocol-designer/src/top-selectors/well-contents/__tests__/wellContentsAllLabware.test.js b/protocol-designer/src/top-selectors/well-contents/__tests__/wellContentsAllLabware.test.js index 3dc9dfe06f6..a7a54fa4fae 100644 --- a/protocol-designer/src/top-selectors/well-contents/__tests__/wellContentsAllLabware.test.js +++ b/protocol-designer/src/top-selectors/well-contents/__tests__/wellContentsAllLabware.test.js @@ -5,7 +5,7 @@ const baseIngredFields = { groupId: '0', name: 'Some Ingred', description: null, - individualize: false, + serialize: false, } const containerState = { From 678dcf4851e433f531e5f3dc66d0c34da3026ece Mon Sep 17 00:00:00 2001 From: IanLondon Date: Mon, 15 Oct 2018 11:35:21 -0400 Subject: [PATCH 3/7] i18n fixups --- .../components/LiquidsPage/LiquidEditForm.js | 26 ++++++++++++------- .../src/localization/en/button.json | 6 +++-- .../src/localization/en/index.js | 2 ++ .../src/localization/en/liquids.json | 7 +++++ 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 protocol-designer/src/localization/en/liquids.json diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js index b826762cb91..3b61ac3aa56 100644 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js @@ -2,6 +2,7 @@ import * as React from 'react' import {connect} from 'react-redux' import assert from 'assert' +import i18n from '../../localization' import * as labwareIngredActions from '../../labware-ingred/actions' import {selectors as labwareIngredSelectors} from '../../labware-ingred/reducers' @@ -35,7 +36,6 @@ type SP = { showForm: boolean, } -// TODO IMMEDIATELY: internationalization of copy class LiquidEditForm extends React.Component { constructor (props: Props) { super(props) @@ -47,7 +47,7 @@ class LiquidEditForm extends React.Component { } updateForm = (fieldName: $Keys) => (e: SyntheticInputEvent<*>) => { - // TODO how to handle checkbox cleanly??? + // need to handle checkbox fields explicitly if (fieldName === 'serialize') { this.setState({[fieldName]: !this.state[fieldName]}) } else { @@ -65,12 +65,16 @@ class LiquidEditForm extends React.Component { return (
-
Details
+
{i18n.t('liquids.details')}
- + - +
@@ -78,14 +82,16 @@ class LiquidEditForm extends React.Component {
Serialization
-

{'Each placement of the liquid will get its own number. ("Sample 1", "Sample 2", "Sample 3")'}

- +

+ {i18n.t('liquids.serialize_explanation')}

+
- DELETE - CANCEL - SAVE + {i18n.t('button.delete')} + {i18n.t('button.cancel')} + {i18n.t('button.save')}
) diff --git a/protocol-designer/src/localization/en/button.json b/protocol-designer/src/localization/en/button.json index ebeac5eaebd..9e8438029ad 100644 --- a/protocol-designer/src/localization/en/button.json +++ b/protocol-designer/src/localization/en/button.json @@ -1,9 +1,11 @@ { "add_step": "+ Add Step", "cancel": "cancel", + "delete": "delete", "done": "done", "no": "no", + "ok": "ok", "reset": "reset", - "yes": "yes", - "ok": "ok" + "save": "save", + "yes": "yes" } diff --git a/protocol-designer/src/localization/en/index.js b/protocol-designer/src/localization/en/index.js index 7a63e42134c..b0184a26d12 100644 --- a/protocol-designer/src/localization/en/index.js +++ b/protocol-designer/src/localization/en/index.js @@ -4,6 +4,7 @@ import alert from './alert.json' import button from './button.json' import card from './card.json' import deck from './deck.json' +import liquids from './liquids.json' import modal from './modal.json' import nav from './nav.json' import step_edit_form from './step_edit_form.json' @@ -16,6 +17,7 @@ export default { button, card, deck, + liquids, modal, nav, step_edit_form, diff --git a/protocol-designer/src/localization/en/liquids.json b/protocol-designer/src/localization/en/liquids.json new file mode 100644 index 00000000000..a5ff0fd4b17 --- /dev/null +++ b/protocol-designer/src/localization/en/liquids.json @@ -0,0 +1,7 @@ +{ + "details": "details", + "description": "Description", + "serialize": "Serialize", + "serialize_explanation": "Each placement of the liquid will get its own number. (\"Sample 1\", \"Sample 2\", \"Sample 3\")", + "liquid_name": "Liquid name" +} From 5e022ef8f5aea66de2ea9e567efcd07e691e5964 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Mon, 15 Oct 2018 11:46:02 -0400 Subject: [PATCH 4/7] fix copy case typo --- protocol-designer/src/localization/en/liquids.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol-designer/src/localization/en/liquids.json b/protocol-designer/src/localization/en/liquids.json index a5ff0fd4b17..b9b228f9339 100644 --- a/protocol-designer/src/localization/en/liquids.json +++ b/protocol-designer/src/localization/en/liquids.json @@ -1,5 +1,5 @@ { - "details": "details", + "details": "Details", "description": "Description", "serialize": "Serialize", "serialize_explanation": "Each placement of the liquid will get its own number. (\"Sample 1\", \"Sample 2\", \"Sample 3\")", From e95e3e545abe1e92e700b1f8905e8b69f48dd078 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Mon, 15 Oct 2018 13:03:27 -0400 Subject: [PATCH 5/7] fixup for PR comments: flow typing cleanup --- .../components/LiquidsPage/LiquidEditForm.js | 2 +- .../src/labware-ingred/actions.js | 18 +++++++++--------- .../src/labware-ingred/reducers/index.js | 4 ++-- protocol-designer/src/labware-ingred/types.js | 4 +--- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js index 3b61ac3aa56..268036d0fa9 100644 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js @@ -21,7 +21,7 @@ import styles from './LiquidEditForm.css' import formStyles from '../forms.css' type Props = { - ...IngredInputs, + ...$Exact, deleteLiquidGroup: () => mixed, cancelForm: () => mixed, saveForm: (IngredInputs) => mixed, diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index 81b047863f9..87702c81f97 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -6,9 +6,11 @@ import {selectors} from './reducers' import wellSelectionSelectors from '../well-selection/selectors' import type {GetState} from '../types' -import type {IngredInputFields} from './types' +import type {IngredInputs} from './types' import type {DeckSlot} from '@opentrons/components' +type IngredInputFields = $Exact + // ===== Labware selector actions ===== export const openAddLabwareModal = createAction( @@ -221,26 +223,24 @@ export function createNewLiquidGroup () { return {type: 'CREATE_NEW_LIQUID_GROUP_FORM'} } -export type EditLiquidGroupAction = { +export type EditLiquidGroupAction = {| type: 'EDIT_LIQUID_GROUP', - payload: { + payload: {| liquidGroupId: string, ...IngredInputFields, - }, -} + |}, +|} // NOTE: with no ID, a new one is assigned export const editLiquidGroup = ( args: {liquidGroupId: ?string, ...IngredInputFields} ) => (dispatch: Dispatch, getState: GetState ) => { - // TODO: Ian 2018-10-12 flow doesn't understand unpacking in: {...args, liquidGroupId: args.id || 'blahId'} + const {liquidGroupId, ...payloadArgs} = args // NOTE: separate liquidGroupId for flow to understand unpacking :/ dispatch({ type: 'EDIT_LIQUID_GROUP', payload: { - name: args.name, - serialize: args.serialize, - description: args.description, + ...payloadArgs, liquidGroupId: args.liquidGroupId || selectors.getNextLiquidGroupId(getState()), }, }) diff --git a/protocol-designer/src/labware-ingred/reducers/index.js b/protocol-designer/src/labware-ingred/reducers/index.js index d1b68f0a837..cd7130ca847 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.js +++ b/protocol-designer/src/labware-ingred/reducers/index.js @@ -20,7 +20,7 @@ import {getIsTiprack} from '@opentrons/shared-data' import type {LabwareLiquidState} from '../../step-generation' import type { - IngredInputFields, + IngredInputs, IngredientGroups, AllIngredGroupFields, IngredientInstance, @@ -452,7 +452,7 @@ const containersBySlot: Selector = createSelector( type IngredGroupFields = { [ingredGroupId: string]: { groupId: string, - ...IngredInputFields, + ...$Exact, }, } const allIngredientGroupFields: Selector = createSelector( diff --git a/protocol-designer/src/labware-ingred/types.js b/protocol-designer/src/labware-ingred/types.js index 99cea829314..a956f434eda 100644 --- a/protocol-designer/src/labware-ingred/types.js +++ b/protocol-designer/src/labware-ingred/types.js @@ -51,11 +51,9 @@ export type IngredInputs = { // TODO: Ian 2018-10-12 rename to 'LiquidFormFields serialize: boolean, } -export type IngredInputFields = $Exact - export type IngredGroupAccessor = $Keys -export type IngredientInstance = $Diff +export type IngredientInstance = $Diff<$Exact, {volume: *}> export type IngredientGroups = { [groupId: string]: IngredientInstance, From 3b592f9b18537829ee01ca0fd199b590b8a523b7 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Mon, 15 Oct 2018 17:44:07 -0400 Subject: [PATCH 6/7] internationalization & flow type cleanups; disable save w/o changes --- .../src/components/IngredientsList.js | 12 ++-- .../LabwareDetailsCard/LabwareDetailsCard.js | 40 ++++++++++++ .../components/LiquidsPage/LiquidEditForm.js | 35 ++++++---- .../src/components/LiquidsSidebar/index.js | 3 +- .../StepEditForm/TipPositionInput/index.js | 2 +- .../WellOrderInput/WellOrderModal.js | 4 +- .../StepEditForm/WellOrderInput/index.js | 2 +- .../src/components/StepEditForm/formFields.js | 4 +- .../src/containers/IngredientsList.js | 2 +- .../src/file-data/selectors/fileCreator.js | 2 +- .../src/labware-ingred/actions.js | 10 +-- .../src/labware-ingred/reducers/index.js | 26 ++++---- protocol-designer/src/labware-ingred/types.js | 14 ++-- .../src/localization/en/button.json | 1 + .../src/localization/en/form.json | 65 +++++++++++++++++++ .../src/localization/en/index.js | 6 +- .../src/localization/en/liquids.json | 7 -- .../src/localization/en/step_edit_form.json | 47 -------------- 18 files changed, 173 insertions(+), 109 deletions(-) create mode 100644 protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.js create mode 100644 protocol-designer/src/localization/en/form.json delete mode 100644 protocol-designer/src/localization/en/liquids.json delete mode 100644 protocol-designer/src/localization/en/step_edit_form.json diff --git a/protocol-designer/src/components/IngredientsList.js b/protocol-designer/src/components/IngredientsList.js index 217ae114962..3358e28fd05 100644 --- a/protocol-designer/src/components/IngredientsList.js +++ b/protocol-designer/src/components/IngredientsList.js @@ -9,7 +9,7 @@ import {PDTitledList, PDListItem} from './lists' import stepItemStyles from './steplist/StepItem.css' import StepDescription from './StepDescription' import styles from './IngredientsList.css' -import type {IngredientGroups, IngredientInstance} from '../labware-ingred/types' +import type {LiquidGroupsById, LiquidGroup} from '../labware-ingred/types' import type {SingleLabwareLiquidState} from '../step-generation' type DeleteIngredient = (args: {|groupId: string, wellName?: string|}) => mixed @@ -24,7 +24,7 @@ type CommonProps = {| type CardProps = {| groupId: string, - ingredGroup: IngredientInstance, + ingredGroup: LiquidGroup, labwareWellContents: SingleLabwareLiquidState, ...CommonProps, |} @@ -148,7 +148,7 @@ function IngredIndividual (props: IndividProps) { type Props = { ...CommonProps, - ingredientGroups: IngredientGroups, + LiquidGroupsById: LiquidGroupsById, labwareWellContents: SingleLabwareLiquidState, selectedIngredientGroupId: ?string, renameLabwareFormMode: boolean, @@ -158,7 +158,7 @@ type Props = { export default function IngredientsList (props: Props) { const { labwareWellContents, - ingredientGroups, + LiquidGroupsById, editModeIngredientGroup, deleteIngredient, selectedIngredientGroupId, @@ -177,12 +177,12 @@ export default function IngredientsList (props: Props) { onClick={openRenameLabwareForm} /> - {Object.keys(ingredientGroups).map((groupIdForCard) => + {Object.keys(LiquidGroupsById).map((groupIdForCard) => ) diff --git a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.js b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.js new file mode 100644 index 00000000000..044b46607c8 --- /dev/null +++ b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.js @@ -0,0 +1,40 @@ +// @flow +import React from 'react' +import cx from 'classnames' +import i18n from '../../../localization' +import {PDTitledList, PDListItem} from '../../lists' +import styles from './labwareDetailsCard.css' + +type Props = { + labwareType: string, + nickname: string, +} + +export default function LabwareDetailsCard (props: Props) { + return ( + + +
+ + {i18n.t('form.labware.type')} + + {props.labwareType} +
+
+ +
+ + {i18n.t('form.liquid.nickname')} + + + {props.nickname} + + {/* TODO: Ian 2018-10-15 TBD in future ticket: pencil icon goes here? See #2428 */} +
+
+
+ ) +} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js index 268036d0fa9..18e3dde4119 100644 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js @@ -6,7 +6,7 @@ import i18n from '../../localization' import * as labwareIngredActions from '../../labware-ingred/actions' import {selectors as labwareIngredSelectors} from '../../labware-ingred/reducers' -import type {IngredInputs} from '../../labware-ingred/types' +import type {LiquidGroup} from '../../labware-ingred/types' import type {BaseState, ThunkDispatch} from '../../types' import { @@ -21,17 +21,17 @@ import styles from './LiquidEditForm.css' import formStyles from '../forms.css' type Props = { - ...$Exact, + ...$Exact, deleteLiquidGroup: () => mixed, cancelForm: () => mixed, - saveForm: (IngredInputs) => mixed, + saveForm: (LiquidGroup) => mixed, } -type State = IngredInputs +type State = LiquidGroup type WrapperProps = {showForm: boolean, formKey: string, formProps: Props} type SP = { - ...IngredInputs, + ...LiquidGroup, _liquidGroupId: ?string, showForm: boolean, } @@ -46,7 +46,7 @@ class LiquidEditForm extends React.Component { } } - updateForm = (fieldName: $Keys) => (e: SyntheticInputEvent<*>) => { + updateForm = (fieldName: $Keys) => (e: SyntheticInputEvent<*>) => { // need to handle checkbox fields explicitly if (fieldName === 'serialize') { this.setState({[fieldName]: !this.state[fieldName]}) @@ -59,21 +59,28 @@ class LiquidEditForm extends React.Component { this.props.saveForm(this.state) } + disableSave = () => { + const hasChanges = Object.keys(this.state).some(field => + (this.state[field] || null) !== (this.props[field]) || null) + console.log(this.props, this.state) + return !hasChanges + } + render () { const {deleteLiquidGroup, cancelForm} = this.props const {name, description, serialize} = this.state return (
-
{i18n.t('liquids.details')}
+
{i18n.t('form.liquid.details')}
@@ -83,7 +90,7 @@ class LiquidEditForm extends React.Component {
Serialization

- {i18n.t('liquids.serialize_explanation')}

+ {i18n.t('form.liquid.serialize_explanation')}

@@ -91,7 +98,11 @@ class LiquidEditForm extends React.Component {
{i18n.t('button.delete')} {i18n.t('button.cancel')} - {i18n.t('button.save')} + + {i18n.t('button.save')} +
) @@ -132,7 +143,7 @@ function mergeProps (stateProps: SP, dispatchProps: {dispatch: ThunkDispatch<*>} ...passThruFormProps, deleteLiquidGroup: () => window.alert('Deleting liquids is not yet implemented'), // TODO: Ian 2018-10-12 later ticket cancelForm: () => dispatch(labwareIngredActions.deselectLiquidGroup()), - saveForm: (formData: IngredInputs) => dispatch(labwareIngredActions.editLiquidGroup({ + saveForm: (formData: LiquidGroup) => dispatch(labwareIngredActions.editLiquidGroup({ ...formData, liquidGroupId: _liquidGroupId, })), diff --git a/protocol-designer/src/components/LiquidsSidebar/index.js b/protocol-designer/src/components/LiquidsSidebar/index.js index 1cff751f5d0..65ed399db7a 100644 --- a/protocol-designer/src/components/LiquidsSidebar/index.js +++ b/protocol-designer/src/components/LiquidsSidebar/index.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react' import {connect} from 'react-redux' +import i18n from '../../localization' import { PrimaryButton, SidePanel, @@ -46,7 +47,7 @@ function LiquidsSidebar (props: Props) { - New Liquid + {i18n.t('button.new_liquid')}
diff --git a/protocol-designer/src/components/StepEditForm/TipPositionInput/index.js b/protocol-designer/src/components/StepEditForm/TipPositionInput/index.js index 1486a1dd9f6..8fa0c4ceec6 100644 --- a/protocol-designer/src/components/StepEditForm/TipPositionInput/index.js +++ b/protocol-designer/src/components/StepEditForm/TipPositionInput/index.js @@ -33,7 +33,7 @@ class TipPositionInput extends React.Component { tooltipComponent={i18n.t('tooltip.step_fields.defaults.tipPosition')} >{(hoverTooltipHandlers) => ( { options={ WELL_ORDER_VALUES.map((value) => ({ value, - name: i18n.t(`step_edit_form.field.well_order.option.${value}`), + name: i18n.t(`form.step_edit_form.field.well_order.option.${value}`), })) } /> {i18n.t('modal.well_order.then')} @@ -123,7 +123,7 @@ class WellOrderModal extends React.Component { options={ WELL_ORDER_VALUES.map((value) => ({ value, - name: i18n.t(`step_edit_form.field.well_order.option.${value}`), + name: i18n.t(`form.step_edit_form.field.well_order.option.${value}`), disabled: this.isSecondOptionDisabled(value), })) } /> diff --git a/protocol-designer/src/components/StepEditForm/WellOrderInput/index.js b/protocol-designer/src/components/StepEditForm/WellOrderInput/index.js index bb8f74a15b8..e60e8843998 100644 --- a/protocol-designer/src/components/StepEditForm/WellOrderInput/index.js +++ b/protocol-designer/src/components/StepEditForm/WellOrderInput/index.js @@ -23,7 +23,7 @@ class WellOrderInput extends React.Component { render () { return ( { const {name, stepType} = props const options = CHANGE_TIP_VALUES.map((value) => ({ value, - name: i18n.t(`step_edit_form.${stepType}.change_tip_option.${value}`), + name: i18n.t(`form.step_edit_form.${stepType}.change_tip_option.${value}`), })) return ( ( ProtocolFile = createSelector( getInitialRobotState, robotStateTimeline, dismissSelectors.getAllDismissedWarnings, - ingredSelectors.getIngredientGroups, + ingredSelectors.getLiquidGroupsById, ingredSelectors.getIngredientLocations, steplistSelectors.getSavedForms, steplistSelectors.orderedSteps, diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index 87702c81f97..82d70afa7c1 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -9,7 +9,7 @@ import type {GetState} from '../types' import type {IngredInputs} from './types' import type {DeckSlot} from '@opentrons/components' -type IngredInputFields = $Exact +type IngredInputsExact = $Exact // ===== Labware selector actions ===== @@ -155,7 +155,7 @@ export const deleteIngredient = (payload: DeleteIngredientPrepayload) => (dispat export type EditIngredient = { type: 'EDIT_INGREDIENT', payload: { - ...IngredInputFields, + ...IngredInputs, containerId: string, groupId: string, wells: Array, @@ -163,7 +163,7 @@ export type EditIngredient = { } export type EditIngredientPayload = { - ...IngredInputFields, + ...IngredInputsExact, groupId: string | null, // null indicates new ingredient is being created } @@ -227,13 +227,13 @@ export type EditLiquidGroupAction = {| type: 'EDIT_LIQUID_GROUP', payload: {| liquidGroupId: string, - ...IngredInputFields, + ...IngredInputsExact, |}, |} // NOTE: with no ID, a new one is assigned export const editLiquidGroup = ( - args: {liquidGroupId: ?string, ...IngredInputFields} + args: {liquidGroupId: ?string, ...IngredInputsExact} ) => (dispatch: Dispatch, getState: GetState ) => { const {liquidGroupId, ...payloadArgs} = args // NOTE: separate liquidGroupId for flow to understand unpacking :/ diff --git a/protocol-designer/src/labware-ingred/reducers/index.js b/protocol-designer/src/labware-ingred/reducers/index.js index cd7130ca847..2fd72f3cc9e 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.js +++ b/protocol-designer/src/labware-ingred/reducers/index.js @@ -21,9 +21,9 @@ import type {LabwareLiquidState} from '../../step-generation' import type { IngredInputs, - IngredientGroups, + LiquidGroupsById, AllIngredGroupFields, - IngredientInstance, + LiquidGroup, OrderedLiquids, Labware, LabwareTypeById, @@ -211,7 +211,7 @@ export const savedLabware = handleActions({ ), }, {}) -type IngredientsState = IngredientGroups +type IngredientsState = LiquidGroupsById export const ingredients = handleActions({ EDIT_LIQUID_GROUP: (state: IngredientsState, action: EditLiquidGroupAction): IngredientsState => { const {liquidGroupId} = action.payload @@ -223,9 +223,9 @@ export const ingredients = handleActions({ EDIT_INGREDIENT: (state, action: EditIngredient) => { // TODO: Ian 2018-10-12 this is deprecated, remove when "add liquids to deck" modal is redone const {groupId, description, serialize, name} = action.payload - const ingredFields: IngredientInstance = { + const ingredFields: LiquidGroup = { description, - serialize, + serialize: Boolean(serialize), name, } @@ -346,17 +346,17 @@ const getLabwareTypes: Selector = createSelector( ) ) -const getIngredientGroups = (state: BaseState) => rootSelector(state).ingredients +const getLiquidGroupsById = (state: BaseState) => rootSelector(state).ingredients const getIngredientLocations = (state: BaseState) => rootSelector(state).ingredLocations const getNextLiquidGroupId: Selector = createSelector( - getIngredientGroups, + getLiquidGroupsById, (_ingredGroups) => ((max(Object.keys(_ingredGroups).map(id => parseInt(id))) + 1) || 0).toString() ) const getIngredientNames: Selector<{[ingredId: string]: string}> = createSelector( - getIngredientGroups, - ingredGroups => mapValues(ingredGroups, (ingred: IngredientInstance) => ingred.name) + getLiquidGroupsById, + ingredGroups => mapValues(ingredGroups, (ingred: LiquidGroup) => ingred.name) ) const _loadedContainersBySlot = (containers: ContainersState) => @@ -456,7 +456,7 @@ type IngredGroupFields = { }, } const allIngredientGroupFields: Selector = createSelector( - getIngredientGroups, + getLiquidGroupsById, (ingreds) => reduce( ingreds, (acc: IngredGroupFields, ingredGroup: IngredGroupFields, ingredGroupId: string) => ({ @@ -466,7 +466,7 @@ const allIngredientGroupFields: Selector = createSelector( ) const allIngredientNamesIds: BaseState => OrderedLiquids = createSelector( - getIngredientGroups, + getLiquidGroupsById, ingreds => Object.keys(ingreds).map(ingredId => ({ingredientId: ingredId, name: ingreds[ingredId].name})) ) @@ -501,13 +501,13 @@ const getRenameLabwareFormMode = (state: BaseState) => rootSelector(state).renam const slotToMoveFrom = (state: BaseState) => rootSelector(state).moveLabwareMode -const hasLiquid = (state: BaseState) => !isEmpty(getIngredientGroups(state)) +const hasLiquid = (state: BaseState) => !isEmpty(getLiquidGroupsById(state)) // TODO: prune selectors export const selectors = { rootSelector, - getIngredientGroups, + getLiquidGroupsById, getIngredientLocations, getIngredientNames, getLabware, diff --git a/protocol-designer/src/labware-ingred/types.js b/protocol-designer/src/labware-ingred/types.js index a956f434eda..263db0f164e 100644 --- a/protocol-designer/src/labware-ingred/types.js +++ b/protocol-designer/src/labware-ingred/types.js @@ -44,19 +44,21 @@ export type OrderedLiquids = Array<{ name: ?string, }> -export type IngredInputs = { // TODO: Ian 2018-10-12 rename to 'LiquidFormFields' +// TODO: Ian 2018-10-15 audit & rename these confusing types +export type LiquidGroup = { name: ?string, - volume?: ?number, description: ?string, serialize: boolean, } -export type IngredGroupAccessor = $Keys +export type IngredInputs = LiquidGroup & { + volume: ?number, +} -export type IngredientInstance = $Diff<$Exact, {volume: *}> +export type IngredGroupAccessor = $Keys -export type IngredientGroups = { - [groupId: string]: IngredientInstance, +export type LiquidGroupsById = { + [groupId: string]: LiquidGroup, } export type AllIngredGroupFields = { diff --git a/protocol-designer/src/localization/en/button.json b/protocol-designer/src/localization/en/button.json index 9e8438029ad..db87b592eb0 100644 --- a/protocol-designer/src/localization/en/button.json +++ b/protocol-designer/src/localization/en/button.json @@ -3,6 +3,7 @@ "cancel": "cancel", "delete": "delete", "done": "done", + "new_liquid": "New Liquid", "no": "no", "ok": "ok", "reset": "reset", diff --git a/protocol-designer/src/localization/en/form.json b/protocol-designer/src/localization/en/form.json new file mode 100644 index 00000000000..59765dae23a --- /dev/null +++ b/protocol-designer/src/localization/en/form.json @@ -0,0 +1,65 @@ +{ +"generic": { + "labware_type": "Type", + "nickname": "Nickname" + }, + "labware": { + "type": "Labware Type" + }, + "liquid": { + "details": "Details", + "description": "Description", + "name": "Liquid name", + "serialize": "Serialize", + "serialize_explanation": "Each placement of the liquid will get its own number. (\"Sample 1\", \"Sample 2\", \"Sample 3\")" + }, + "step_edit_form": { + "step_edit_form": { + "consolidate": { + "change_tip_option": { + "always": "For each dispense", + "once": "Only the first aspirate", + "never": "Never" + } + }, + "distribute": { + "change_tip_option": { + "always": "For each aspirate", + "once": "Only the first aspirate", + "never": "Never" + } + }, + "mix": { + "change_tip_option": { + "always": "For each well", + "once": "Only the first aspirate", + "never": "Never" + } + }, + "transfer": { + "change_tip_option": { + "always": "For each aspirate", + "once": "Only the first aspirate", + "never": "Never" + } + }, + "field": { + "change_tip": { + "label": "Get new tip" + }, + "tip_position": { + "label": "Tip Position" + }, + "well_order": { + "label": "Well Order", + "option": { + "l2r": "Left to right", + "r2l": "Right to left", + "t2b": "Top to bottom", + "b2t": "Bottom to top" + } + } + } + } + } +} diff --git a/protocol-designer/src/localization/en/index.js b/protocol-designer/src/localization/en/index.js index b0184a26d12..22235ff6b04 100644 --- a/protocol-designer/src/localization/en/index.js +++ b/protocol-designer/src/localization/en/index.js @@ -4,10 +4,9 @@ import alert from './alert.json' import button from './button.json' import card from './card.json' import deck from './deck.json' -import liquids from './liquids.json' +import form from './form.json' import modal from './modal.json' import nav from './nav.json' -import step_edit_form from './step_edit_form.json' import tooltip from './tooltip.json' import well_selection from './well_selection.json' @@ -17,10 +16,9 @@ export default { button, card, deck, - liquids, + form, modal, nav, - step_edit_form, tooltip, well_selection, }, diff --git a/protocol-designer/src/localization/en/liquids.json b/protocol-designer/src/localization/en/liquids.json deleted file mode 100644 index b9b228f9339..00000000000 --- a/protocol-designer/src/localization/en/liquids.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "details": "Details", - "description": "Description", - "serialize": "Serialize", - "serialize_explanation": "Each placement of the liquid will get its own number. (\"Sample 1\", \"Sample 2\", \"Sample 3\")", - "liquid_name": "Liquid name" -} diff --git a/protocol-designer/src/localization/en/step_edit_form.json b/protocol-designer/src/localization/en/step_edit_form.json deleted file mode 100644 index 6e54eaa5096..00000000000 --- a/protocol-designer/src/localization/en/step_edit_form.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "consolidate": { - "change_tip_option": { - "always": "For each dispense", - "once": "Only the first aspirate", - "never": "Never" - } - }, - "distribute": { - "change_tip_option": { - "always": "For each aspirate", - "once": "Only the first aspirate", - "never": "Never" - } - }, - "mix": { - "change_tip_option": { - "always": "For each well", - "once": "Only the first aspirate", - "never": "Never" - } - }, - "transfer": { - "change_tip_option": { - "always": "For each aspirate", - "once": "Only the first aspirate", - "never": "Never" - } - }, - "field": { - "change_tip": { - "label": "Get new tip" - }, - "tip_position": { - "label": "Tip Position" - }, - "well_order": { - "label": "Well Order", - "option": { - "l2r": "Left to right", - "r2l": "Right to left", - "t2b": "Top to bottom", - "b2t": "Bottom to top" - } - } - } -} \ No newline at end of file From 2d8035c7dfad4ffc24221e0de5d27724fda66e32 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Tue, 16 Oct 2018 09:55:03 -0400 Subject: [PATCH 7/7] cleanup :/ --- protocol-designer/src/components/IngredientsList.js | 8 ++++---- .../src/components/LiquidsPage/LiquidEditForm.js | 4 ++-- protocol-designer/src/containers/IngredientsList.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/protocol-designer/src/components/IngredientsList.js b/protocol-designer/src/components/IngredientsList.js index 3358e28fd05..67467bfb72c 100644 --- a/protocol-designer/src/components/IngredientsList.js +++ b/protocol-designer/src/components/IngredientsList.js @@ -148,7 +148,7 @@ function IngredIndividual (props: IndividProps) { type Props = { ...CommonProps, - LiquidGroupsById: LiquidGroupsById, + liquidGroupsById: LiquidGroupsById, labwareWellContents: SingleLabwareLiquidState, selectedIngredientGroupId: ?string, renameLabwareFormMode: boolean, @@ -158,7 +158,7 @@ type Props = { export default function IngredientsList (props: Props) { const { labwareWellContents, - LiquidGroupsById, + liquidGroupsById, editModeIngredientGroup, deleteIngredient, selectedIngredientGroupId, @@ -177,12 +177,12 @@ export default function IngredientsList (props: Props) { onClick={openRenameLabwareForm} /> - {Object.keys(LiquidGroupsById).map((groupIdForCard) => + {Object.keys(liquidGroupsById).map((groupIdForCard) => ) diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js index 18e3dde4119..f13be820c58 100644 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js +++ b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.js @@ -61,8 +61,8 @@ class LiquidEditForm extends React.Component { disableSave = () => { const hasChanges = Object.keys(this.state).some(field => - (this.state[field] || null) !== (this.props[field]) || null) - console.log(this.props, this.state) + (this.state[field] || null) !== (this.props[field] || null) + ) return !hasChanges } diff --git a/protocol-designer/src/containers/IngredientsList.js b/protocol-designer/src/containers/IngredientsList.js index b8ae5146cc4..05f2c7ca5e6 100644 --- a/protocol-designer/src/containers/IngredientsList.js +++ b/protocol-designer/src/containers/IngredientsList.js @@ -23,7 +23,7 @@ function mapStateToProps (state: BaseState): SP { return { renameLabwareFormMode: selectors.getRenameLabwareFormMode(state), - LiquidGroupsById: selectors.getLiquidGroupsById(state), + liquidGroupsById: selectors.getLiquidGroupsById(state), labwareWellContents: (container && selectors.getIngredientLocations(state)[container.id]) || {}, selectedIngredientGroupId: wellSelectionSelectors.getSelectedWellsCommonIngredId(state), selected: false,