From 21e76583bd0a95bae2baa32b72900fa6b18494c5 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 3 Oct 2018 17:03:16 -0400 Subject: [PATCH 01/27] split out selectable labware component begin --- .../src/components/WellSelectionModal.js | 10 +- .../components/labware/SelectableLabware.js | 158 ++++++++++++++++++ .../src/components/labware/index.js | 2 + 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 protocol-designer/src/components/labware/SelectableLabware.js diff --git a/protocol-designer/src/components/WellSelectionModal.js b/protocol-designer/src/components/WellSelectionModal.js index 19b19f75433..0a4f5b5ff11 100644 --- a/protocol-designer/src/components/WellSelectionModal.js +++ b/protocol-designer/src/components/WellSelectionModal.js @@ -2,7 +2,7 @@ import * as React from 'react' import cx from 'classnames' -import SelectablePlate from '../containers/SelectablePlate' +import {SelectableLabware} from '../components/labware' import SingleLabwareWrapper from '../components/SingleLabware' import WellSelectionInstructions from './WellSelectionInstructions' @@ -21,7 +21,8 @@ type Props = { } export default function WellSelectionModal (props: Props) { - const pipetteConfig = props.pipette && getPipette(props.pipette.model) + const pipette = props + const pipetteConfig = pipette && getPipette(pipette.model) return ( - + diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js new file mode 100644 index 00000000000..3df464a39f1 --- /dev/null +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -0,0 +1,158 @@ +// @flow +import * as React from 'react' +import omit from 'lodash/omit' +import reduce from 'lodash/reduce' +import {connect} from 'react-redux' +import { + swatchColors, + Labware, + LabwareLabels, + MIXED_WELL_COLOR, + type Channels, +} from '@opentrons/components' + +import {getCollidingWells} from '../../utils' +import {SELECTABLE_WELL_CLASS} from '../../constants' +import {getWellSetForMultichannel} from '../../well-selection/utils' +import SelectionRect from '../SelectionRect.js' +import type {ContentsByWell, Wells} from '../../labware-ingred/types' + +import * as wellContentsSelectors from '../../top-selectors/well-contents' +import {selectors} from '../../labware-ingred/reducers' +import {selectors as steplistSelectors} from '../../steplist' +import type {GenericRect} from '../../collision-types' +import type {BaseState} from '../../types' + +type LabwareProps = React.ElementProps + +export type Props = { + wellContents: ContentsByWell, + getTipProps?: $PropertyType, + containerType: string, + updateSelectedWells: (Wells) => mixed, + + // used by container + containerId: string, + pipetteChannels?: ?Channels, +} + +// TODO Ian 2018-07-20: make sure '__air__' or other pseudo-ingredients don't get in here +function getFillColor (groupIds: Array): ?string { + if (groupIds.length === 0) return null + if (groupIds.length === 1) return swatchColors(Number(groupIds[0])) + return MIXED_WELL_COLOR +} + +class SelectableLabware extends React.Component { + constructor (props) { + super(props) + const initialSelectedWells = reduce(this.props.wellContents, (acc, well) => ( + well.highlighted ? {...acc, [well]: well} : acc + ), {}) + this.state = {selectedWells: initialSelectedWells, highlightedWells: {}} + } + + _getWellsFromRect = (rect: GenericRect): * => { + const selectedWells = getCollidingWells(rect, SELECTABLE_WELL_CLASS) + return this._wellsFromSelected(selectedWells) + } + + _wellsFromSelected = (selectedWells: Wells): Wells => { + // Returns PRIMARY WELLS from the selection. + if (this.props.pipetteChannels === 8) { + // for the wells that have been highlighted, + // get all 8-well well sets and merge them + const primaryWells: Wells = Object.keys(selectedWells).reduce((acc: Wells, well: string): Wells => { + const wellSet = getWellSetForMultichannel(this.props.containerType, well) + if (!wellSet) return acc + + const primaryWell = wellSet[0] + + return { ...acc, [primaryWell]: primaryWell } + }, {}) + + return primaryWells + } + + // single-channel or ingred selection mode + return selectedWells + } + + handleSelectionMove = (e, rect) => { + if (!e.shiftKey) { + this.setState({highlightedWells: this._getWellsFromRect(rect)}) + } + } + handleSelectionDone = (e, rect) => { + const wells = this._getWellsFromRect(rect) + const nextSelectedWells = e.shiftKey + ? omit(this.state.selectedWells, Object.keys(wells)) + : {...this.state.selectedWells, ...wells} + this.setState({selectedWells: nextSelectedWells, highlightedWells: {}}) + this.props.updateSelectedWells(nextSelectedWells) + } + + render () { + const {wellContents, getTipProps, containerType} = this.props + + const getWellProps = (wellName) => { + const well = wellContents[wellName] + + return { + wellName, + highlighted: Object.keys(this.state.highlightedWells).includes(wellName), + selected: Object.keys(this.state.selectedWells).includes(wellName), + error: well.error, + maxVolume: well.maxVolume, + fillColor: getFillColor(well.groupIds), + } + } + + return ( + + + + + ) + } +} + +type SP = $Diff + +function mapStateToProps (state: BaseState, ownProps: OP): SP { + // const {selectable} = ownProps + const selectedContainerId = selectors.getSelectedContainerId(state) + const containerId = ownProps.containerId || selectedContainerId + + if (containerId === null) { + console.error('SelectablePlate: No container is selected, and no containerId was given to Connected SelectablePlate') + return {containerId: '', wellContents: {}, containerType: ''} + } + + const labware = selectors.getLabware(state)[containerId] + const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) + let wellContents: ContentsByWell = {} + + const stepId = steplistSelectors.getActiveItem(state).id + // TODO: Ian 2018-07-31 replace with util function, "findIndexOrNull"? + const orderedSteps = steplistSelectors.orderedSteps(state) + const timelineIdx = orderedSteps.includes(stepId) + ? orderedSteps.findIndex(id => id === stepId) + : null + + if ((timelineIdx != null)) { + wellContents = (allWellContentsForSteps[timelineIdx]) + // Valid non-end step + ? allWellContentsForSteps[timelineIdx][containerId] + // Erroring step: show last valid well contents in timeline + : wellContentsSelectors.lastValidWellContents(state)[containerId] + } + + return { + containerId, + wellContents, + containerType: labware ? labware.type : 'missing labware', + } +} + +export default connect(mapStateToProps)(SelectableLabware) diff --git a/protocol-designer/src/components/labware/index.js b/protocol-designer/src/components/labware/index.js index b7b058ef936..d2d6f43bba8 100644 --- a/protocol-designer/src/components/labware/index.js +++ b/protocol-designer/src/components/labware/index.js @@ -1,8 +1,10 @@ // @flow import LabwareOnDeck from './LabwareOnDeck' import NameThisLabwareOverlay from './NameThisLabwareOverlay' +import SelectableLabware from './SelectableLabware' export { LabwareOnDeck, NameThisLabwareOverlay, + SelectableLabware, } From c0b60b713a28ef417156bfa7cb42e14591e27934 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 4 Oct 2018 10:58:36 -0400 Subject: [PATCH 02/27] selecting wells --- protocol-designer/src/components/labware/SelectableLabware.js | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 3df464a39f1..58e1733ef65 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -99,6 +99,7 @@ class SelectableLabware extends React.Component { const well = wellContents[wellName] return { + selectable: true, wellName, highlighted: Object.keys(this.state.highlightedWells).includes(wellName), selected: Object.keys(this.state.selectedWells).includes(wellName), From d4f2ab963362e71657fc59019e96f5539075a0f7 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 4 Oct 2018 16:53:46 -0400 Subject: [PATCH 03/27] clean up the edges of the new well selection modal and input --- .../src/components/ProtocolEditor.js | 2 - .../WellSelectionInput/WellSelectionInput.js | 48 ++++-- .../WellSelectionModal.css | 0 .../WellSelectionInput/WellSelectionModal.js | 148 ++++++++++++++++++ .../StepEditForm/WellSelectionInput/index.js | 2 + .../src/components/WellSelectionModal.js | 51 ------ .../components/labware/SelectableLabware.js | 90 +++-------- .../containers/ConnectedWellSelectionModal.js | 55 ------- 8 files changed, 210 insertions(+), 186 deletions(-) rename protocol-designer/src/components/{ => StepEditForm/WellSelectionInput}/WellSelectionModal.css (100%) create mode 100644 protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js delete mode 100644 protocol-designer/src/components/WellSelectionModal.js delete mode 100644 protocol-designer/src/containers/ConnectedWellSelectionModal.js diff --git a/protocol-designer/src/components/ProtocolEditor.js b/protocol-designer/src/components/ProtocolEditor.js index bc28c2d7377..b1806d8501f 100644 --- a/protocol-designer/src/components/ProtocolEditor.js +++ b/protocol-designer/src/components/ProtocolEditor.js @@ -10,7 +10,6 @@ import ConnectedSidebar from '../containers/ConnectedSidebar' import ConnectedTitleBar from '../containers/ConnectedTitleBar' import ConnectedMainPanel from '../containers/ConnectedMainPanel' import ConnectedNewFileModal from '../containers/ConnectedNewFileModal' -import ConnectedWellSelectionModal from '../containers/ConnectedWellSelectionModal' import FileUploadErrorModal from './modals/FileUploadErrorModal' import AnalyticsModal from './modals/AnalyticsModal' import {PortalRoot as MainPageModalPortalRoot} from '../components/portals/MainPageModalPortal' @@ -38,7 +37,6 @@ export default function ProtocolEditor () { - {/* TODO: Ian 2018-06-28 All main page modals will go here */} diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js index 232fa5404a1..1e20a1ac489 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js @@ -1,6 +1,8 @@ // @flow import * as React from 'react' import {FormGroup, InputField} from '@opentrons/components' +import WellSelectionModal from './WellSelectionModal' +import {Portal} from '../../portals/MainPageModalPortal' import type {StepFieldName} from '../../../steplist/fieldLevel' import styles from '../StepEditForm.css' @@ -13,19 +15,39 @@ type Props = { isMulti: ?boolean, } -export default function WellSelectionInput (props: Props) { - return ( - +type State = {isModalOpen: boolean} + +class WellSelectionInput extends React.Component { + state = {isModalOpen: false} + + toggleModal = () => { + this.setState({isModalOpen: !this.state.isModalOpen}) + } + + render () { + return ( + - - ) + name={this.props.name} + value={this.props.primaryWellCount ? String(this.props.primaryWellCount) : null} + onClick={this.toggleModal} + error={this.props.errorToShow} /> + + + + + ) + } } + +export default WellSelectionInput diff --git a/protocol-designer/src/components/WellSelectionModal.css b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.css similarity index 100% rename from protocol-designer/src/components/WellSelectionModal.css rename to protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.css diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js new file mode 100644 index 00000000000..43ac07d3558 --- /dev/null +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -0,0 +1,148 @@ +// @flow +import * as React from 'react' +import cx from 'classnames' +import omit from 'lodash/omit' +import reduce from 'lodash/reduce' +import {connect} from 'react-redux' + +import {Modal, OutlineButton, LabeledValue} from '@opentrons/components' +import {getPipette} from '@opentrons/shared-data' +import {sortWells} from '../../../utils' +import type {BaseState, ThunkDispatch} from '../../../types' +import {selectors as pipetteSelectors} from '../../../pipettes' + +import * as wellContentsSelectors from '../../../top-selectors/well-contents' +import {selectors} from '../../../labware-ingred/reducers' +import {selectors as steplistSelectors} from '../../../steplist' +import {changeFormInput} from '../../../steplist/actions' +import type {StepFieldName} from '../../../steplist/fieldLevel' + +import {SelectableLabware} from '../../labware' +import SingleLabwareWrapper from '../../SingleLabware' +import WellSelectionInstructions from '../../WellSelectionInstructions' +import type {PipetteData} from '../step-generation/types' +import type {Wells, ContentsByWell} from '../labware-ingred/types' + +import styles from './WellSelectionModal.css' +import modalStyles from '../../modals/modal.css' + +type OP = { + pipetteId: string, + labwareId: string, + isOpen: boolean, + onCloseClick: (e: SyntheticEvent<*>) => mixed, + name: StepFieldName, +} +type SP = { + pipette: ?PipetteData, + initialSelectedWells: Array, + wellContents: ContentsByWell, + containerType: string, +} +type DP = {saveWellSelection: () => mixed} + +type Props = OP & SP & DP +type State = {selectedWells: Wells, highlightedWells: Wells} + +class WellSelectionModal extends React.Component { + state = {selectedWells: {}, highlightedWells: {}} + constructor (props) { + super(props) + const initialSelectedWells = reduce(this.props.initialSelectedWells, (acc, well) => ( + {...acc, [well]: well} + ), {}) + this.state = {selectedWells: initialSelectedWells, highlightedWells: {}} + } + + updateHighlightedWells = (wells: Wells) => { + this.setState({highlightedWells: wells}) + } + + selectWells = (wells: Wells) => { + this.setState({selectedWells: {...this.state.selectedWells, ...wells}, highlightedWells: {}}) + } + + deselectWells = (wells: Wells) => { + this.setState({selectedWells: omit(this.state.selectedWells, Object.keys(wells))}) + } + + handleSave = () => { + this.props.saveWellSelection(this.state.selectedWells) + this.props.onCloseClick() + } + + render () { + if (!this.props.isOpen) return null + const {pipette} = this.props + const pipetteConfig = pipette && getPipette(pipette.model) + + return ( + +
+ + + SAVE SELECTION + +
+ + + + + + +
+ ) + } +} + +type DP = { + saveWellSelection: $PropertyType, +} + +type SP = $Diff + +function mapStateToProps (state: BaseState, ownProps: OP): SP { + const {pipetteId, labwareId} = ownProps + + const labware = selectors.getLabware(state)[labwareId] + const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) + + const stepId = steplistSelectors.getActiveItem(state).id + // TODO: Ian 2018-07-31 replace with util function, "findIndexOrNull"? + const orderedSteps = steplistSelectors.orderedSteps(state) + const timelineIdx = orderedSteps.findIndex(id => id === stepId) + const formData = steplistSelectors.getUnsavedForm(state) + + return { + initialSelectedWells: formData ? formData[ownProps.name] : null, + pipette: pipetteSelectors.equippedPipettes(state)[pipetteId], + wellContents: labware ? allWellContentsForSteps[timelineIdx][labware.id] : {}, + containerType: labware ? labware.type : 'missing labware', + } +} + +function mapDispatchToProps (dispatch: ThunkDispatch<*>, ownProps: OP): DP { + return { + saveWellSelection: (wells) => dispatch(changeFormInput({ + update: {[ownProps.name]: Object.keys(wells).sort(sortWells)}, + })), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(WellSelectionModal) diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js index d00249890d3..aadef47c69c 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js @@ -71,6 +71,8 @@ function mergeProps ( return { name, disabled, + pipetteId: _pipetteId, + labwareId: _selectedLabwareId, isMulti: stateProps.isMulti, primaryWellCount: stateProps.primaryWellCount, errorToShow: showErrors ? _wellFieldErrors[0] : null, diff --git a/protocol-designer/src/components/WellSelectionModal.js b/protocol-designer/src/components/WellSelectionModal.js deleted file mode 100644 index 0a4f5b5ff11..00000000000 --- a/protocol-designer/src/components/WellSelectionModal.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import * as React from 'react' -import cx from 'classnames' - -import {SelectableLabware} from '../components/labware' -import SingleLabwareWrapper from '../components/SingleLabware' -import WellSelectionInstructions from './WellSelectionInstructions' - -import {Modal, OutlineButton, LabeledValue} from '@opentrons/components' -import {getPipette} from '@opentrons/shared-data' - -import type {PipetteData} from '../step-generation/types' - -import styles from './WellSelectionModal.css' -import modalStyles from './modals/modal.css' - -type Props = { - pipette: ?PipetteData, - onCloseClick: (e: SyntheticEvent<*>) => mixed, - onSave: () => mixed, -} - -export default function WellSelectionModal (props: Props) { - const pipette = props - const pipetteConfig = pipette && getPipette(pipette.model) - - return ( - -
- - - SAVE SELECTION - -
- - - - - - -
- ) -} diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 58e1733ef65..48017ff50c3 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -1,8 +1,7 @@ // @flow import * as React from 'react' -import omit from 'lodash/omit' -import reduce from 'lodash/reduce' -import {connect} from 'react-redux' +// import mapValues from 'lodash/mapValues' +// import {connect} from 'react-redux' import { swatchColors, Labware, @@ -17,11 +16,12 @@ import {getWellSetForMultichannel} from '../../well-selection/utils' import SelectionRect from '../SelectionRect.js' import type {ContentsByWell, Wells} from '../../labware-ingred/types' -import * as wellContentsSelectors from '../../top-selectors/well-contents' -import {selectors} from '../../labware-ingred/reducers' -import {selectors as steplistSelectors} from '../../steplist' +// import * as wellContentsSelectors from '../../top-selectors/well-contents' +// import wellSelectionSelectors from '../../well-selection/selectors' +// import {selectors} from '../../labware-ingred/reducers' +// import {selectors as steplistSelectors} from '../../steplist' import type {GenericRect} from '../../collision-types' -import type {BaseState} from '../../types' +// import type {BaseState} from '../../types' type LabwareProps = React.ElementProps @@ -44,14 +44,6 @@ function getFillColor (groupIds: Array): ?string { } class SelectableLabware extends React.Component { - constructor (props) { - super(props) - const initialSelectedWells = reduce(this.props.wellContents, (acc, well) => ( - well.highlighted ? {...acc, [well]: well} : acc - ), {}) - this.state = {selectedWells: initialSelectedWells, highlightedWells: {}} - } - _getWellsFromRect = (rect: GenericRect): * => { const selectedWells = getCollidingWells(rect, SELECTABLE_WELL_CLASS) return this._wellsFromSelected(selectedWells) @@ -80,20 +72,26 @@ class SelectableLabware extends React.Component { handleSelectionMove = (e, rect) => { if (!e.shiftKey) { - this.setState({highlightedWells: this._getWellsFromRect(rect)}) + this.props.updateHighlightedWells(this._getWellsFromRect(rect)) } } handleSelectionDone = (e, rect) => { const wells = this._getWellsFromRect(rect) - const nextSelectedWells = e.shiftKey - ? omit(this.state.selectedWells, Object.keys(wells)) - : {...this.state.selectedWells, ...wells} - this.setState({selectedWells: nextSelectedWells, highlightedWells: {}}) - this.props.updateSelectedWells(nextSelectedWells) + if (e.shiftKey) { + this.props.deselectWells(wells) + } else { + this.props.selectWells(wells) + } } render () { - const {wellContents, getTipProps, containerType} = this.props + const { + wellContents, + getTipProps, + containerType, + highlightedWells, + selectedWells, + } = this.props const getWellProps = (wellName) => { const well = wellContents[wellName] @@ -101,10 +99,10 @@ class SelectableLabware extends React.Component { return { selectable: true, wellName, - highlighted: Object.keys(this.state.highlightedWells).includes(wellName), - selected: Object.keys(this.state.selectedWells).includes(wellName), - error: well.error, - maxVolume: well.maxVolume, + highlighted: Object.keys(highlightedWells).includes(wellName), + selected: Object.keys(selectedWells).includes(wellName), + error: well && well.error, + maxVolume: well && well.maxVolume, fillColor: getFillColor(well.groupIds), } } @@ -118,42 +116,4 @@ class SelectableLabware extends React.Component { } } -type SP = $Diff - -function mapStateToProps (state: BaseState, ownProps: OP): SP { - // const {selectable} = ownProps - const selectedContainerId = selectors.getSelectedContainerId(state) - const containerId = ownProps.containerId || selectedContainerId - - if (containerId === null) { - console.error('SelectablePlate: No container is selected, and no containerId was given to Connected SelectablePlate') - return {containerId: '', wellContents: {}, containerType: ''} - } - - const labware = selectors.getLabware(state)[containerId] - const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) - let wellContents: ContentsByWell = {} - - const stepId = steplistSelectors.getActiveItem(state).id - // TODO: Ian 2018-07-31 replace with util function, "findIndexOrNull"? - const orderedSteps = steplistSelectors.orderedSteps(state) - const timelineIdx = orderedSteps.includes(stepId) - ? orderedSteps.findIndex(id => id === stepId) - : null - - if ((timelineIdx != null)) { - wellContents = (allWellContentsForSteps[timelineIdx]) - // Valid non-end step - ? allWellContentsForSteps[timelineIdx][containerId] - // Erroring step: show last valid well contents in timeline - : wellContentsSelectors.lastValidWellContents(state)[containerId] - } - - return { - containerId, - wellContents, - containerType: labware ? labware.type : 'missing labware', - } -} - -export default connect(mapStateToProps)(SelectableLabware) +export default SelectableLabware diff --git a/protocol-designer/src/containers/ConnectedWellSelectionModal.js b/protocol-designer/src/containers/ConnectedWellSelectionModal.js deleted file mode 100644 index b07d4100d54..00000000000 --- a/protocol-designer/src/containers/ConnectedWellSelectionModal.js +++ /dev/null @@ -1,55 +0,0 @@ -// @flow -import * as React from 'react' -import WellSelectionModal from '../components/WellSelectionModal' -import {connect} from 'react-redux' -import type {BaseState, ThunkDispatch} from '../types' -import wellSelectionSelectors from '../well-selection/selectors' -import {selectors as pipetteSelectors} from '../pipettes' -import { - closeWellSelectionModal, - saveWellSelectionModal, -} from '../well-selection/actions' - -type Props = { - ...React.ElementProps, - hideModal?: boolean, -} - -function WellSelectionModalWrapper (props: Props) { - if (props.hideModal) { - return null - } - return -} - -type DP = { - onSave: $PropertyType, - onCloseClick: $PropertyType, -} - -type SP = $Diff - -function mapStateToProps (state: BaseState): SP { - const wellSelectionModalData = wellSelectionSelectors.wellSelectionModalData(state) - - if (!wellSelectionModalData) { - return { - hideModal: true, - } - } - - const pipetteId = wellSelectionModalData.pipetteId - - return { - pipette: pipetteSelectors.equippedPipettes(state)[pipetteId], - } -} - -function mapDispatchToProps (dispatch: ThunkDispatch<*>): DP { - return { - onSave: () => dispatch(saveWellSelectionModal()), - onCloseClick: () => dispatch(closeWellSelectionModal()), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(WellSelectionModalWrapper) From 3c4bad34aee817f75e3ab5399ea8df2e2d4db1f9 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 4 Oct 2018 18:05:38 -0400 Subject: [PATCH 04/27] should component update in the labware or well, and disconnect ingred modal from global state --- .../WellSelectionInput/WellSelectionModal.js | 6 ----- .../components/labware/SelectableLabware.js | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 43ac07d3558..46f12528a23 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -111,12 +111,6 @@ class WellSelectionModal extends React.Component { } } -type DP = { - saveWellSelection: $PropertyType, -} - -type SP = $Diff - function mapStateToProps (state: BaseState, ownProps: OP): SP { const {pipetteId, labwareId} = ownProps diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 48017ff50c3..e95bb1cfcc2 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react' -// import mapValues from 'lodash/mapValues' +import reduce from 'lodash/reduce' // import {connect} from 'react-redux' import { swatchColors, @@ -58,9 +58,8 @@ class SelectableLabware extends React.Component { const wellSet = getWellSetForMultichannel(this.props.containerType, well) if (!wellSet) return acc - const primaryWell = wellSet[0] - - return { ...acc, [primaryWell]: primaryWell } + const selectedWellsFromSet = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) + return {...acc, ...selectedWellsFromSet} }, {}) return primaryWells @@ -84,6 +83,20 @@ class SelectableLabware extends React.Component { } } + makeHandleMouseOverWell = (wellName) => () => { + if (this.props.pipetteChannels === 8) { + const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) + const nextHighlightedWells = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) + this.props.updateHighlightedWells(nextHighlightedWells) + } else { + this.props.updateHighlightedWells({[wellName]: wellName}) + } + } + + handleMouseExitWell = () => { + this.props.updateHighlightedWells({}) + } + render () { const { wellContents, @@ -99,6 +112,8 @@ class SelectableLabware extends React.Component { return { selectable: true, wellName, + onMouseOver: this.makeHandleMouseOverWell(wellName), + onMouseLeave: this.handleMouseExitWell, highlighted: Object.keys(highlightedWells).includes(wellName), selected: Object.keys(selectedWells).includes(wellName), error: well && well.error, From 7c780583fe43bfe1e9114dfb826311a78db8bfaf Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 14:43:00 -0400 Subject: [PATCH 05/27] labware on deck no hover or select --- components/src/deck/Labware.js | 34 ++-- components/src/deck/Well.js | 149 ++++++++++-------- components/src/deck/index.js | 4 + .../src/components/SelectablePlate.css | 6 + .../src/components/SelectablePlate.js | 107 +++++++++---- .../src/components/labware/LabwareOnDeck.js | 147 +++++++++-------- .../components/labware/SelectableLabware.js | 40 ++--- 7 files changed, 285 insertions(+), 202 deletions(-) create mode 100644 protocol-designer/src/components/SelectablePlate.css diff --git a/components/src/deck/Labware.js b/components/src/deck/Labware.js index 8652fc8f41d..63dbb3141d3 100644 --- a/components/src/deck/Labware.js +++ b/components/src/deck/Labware.js @@ -87,21 +87,25 @@ function createWell ( /> } -export default function Labware (props: Props) { - const {labwareType, getTipProps, getWellProps} = props +class Labware extends React.Component { + render () { + const {labwareType, getTipProps, getWellProps} = this.props - if (!(getLabware(labwareType))) { - return - } + if (!(getLabware(labwareType))) { + return + } - const allWellNames = Object.keys(getWellDefsForSVG(labwareType)) - const isTiprack = getIsTiprack(labwareType) - const wells = allWellNames.map(wellName => createWell(wellName, labwareType, getTipProps, getWellProps)) - - return ( - - - {wells} - - ) + const allWellNames = Object.keys(getWellDefsForSVG(labwareType)) + const isTiprack = getIsTiprack(labwareType) + const wells = allWellNames.map(wellName => createWell(wellName, labwareType, getTipProps, getWellProps)) + + return ( + + + {wells} + + ) + } } + +export default Labware diff --git a/components/src/deck/Well.js b/components/src/deck/Well.js index 9e31e75e46f..6d92f2a03e1 100644 --- a/components/src/deck/Well.js +++ b/components/src/deck/Well.js @@ -24,86 +24,95 @@ type Props = { onMouseLeave?: (e: SyntheticMouseEvent<*>) => mixed, } -export default function Well (props: Props) { - const { - wellName, - selectable, - highlighted, - selected, - error, - wellDef, - onMouseOver, - onMouseLeave, - } = props +class Well extends React.Component { + shouldComponentUpdate (nextProps) { + return this.props.highlighted !== nextProps.highlighted || + this.props.selected !== nextProps.selected || + this.props.fillColor !== nextProps.fillColor + } + render () { + const { + wellName, + selectable, + highlighted, + selected, + error, + wellDef, + onMouseOver, + onMouseLeave, + } = this.props + + const fillColor = this.props.fillColor || 'transparent' - const fillColor = props.fillColor || 'transparent' + const wellOverlayClassname = cx( + styles.well_border, + { + [SELECTABLE_WELL_CLASS]: selectable, + [styles.selected]: selected, + [styles.selected_overlay]: selected, + [styles.highlighted]: highlighted, + [styles.error]: error, + } + ) - const wellOverlayClassname = cx( - styles.well_border, - { - [SELECTABLE_WELL_CLASS]: selectable, - [styles.selected]: selected, - [styles.selected_overlay]: selected, - [styles.highlighted]: highlighted, - [styles.error]: error, + const selectionProps = { + 'data-wellname': wellName, + onMouseOver, + onMouseLeave, } - ) - const selectionProps = { - 'data-wellname': wellName, - onMouseOver, - onMouseLeave, - } + const isRect = wellIsRect(wellDef) + const isCircle = !isRect - const isRect = wellIsRect(wellDef) - const isCircle = !isRect + if (isRect) { + const rectProps = { + x: wellDef.x, + y: wellDef.y - (wellDef.length || 0), // zero fallback for flow + width: wellDef.width, + height: wellDef.y, + } - if (isRect) { - const rectProps = { - x: wellDef.x, - y: wellDef.y - (wellDef.length || 0), // zero fallback for flow - width: wellDef.width, - height: wellDef.y, + return + {/* Fill contents */} + + {/* Border + overlay */} + + } - return - {/* Fill contents */} - - {/* Border + overlay */} - - - } + if (isCircle) { + const circleProps = { + cx: wellDef.x, + cy: wellDef.y, + r: (wellDef.diameter || 0) / 2, + } - if (isCircle) { - const circleProps = { - cx: wellDef.x, - cy: wellDef.y, - r: (wellDef.diameter || 0) / 2, + return + {/* Fill contents */} + + {/* Border + overlay */} + + } - return - {/* Fill contents */} - - {/* Border + overlay */} - - + console.warn('Invalid well: neither rectangle or circle: ' + JSON.stringify(wellDef)) } - - console.warn('Invalid well: neither rectangle or circle: ' + JSON.stringify(wellDef)) } + +export default Well diff --git a/components/src/deck/index.js b/components/src/deck/index.js index c62749ddc21..cf4ace83f3b 100644 --- a/components/src/deck/index.js +++ b/components/src/deck/index.js @@ -2,8 +2,10 @@ import Deck from './Deck' import Labware from './Labware' import LabwareContainer from './LabwareContainer' +import LabwareOutline from './LabwareOutline' import LabwareLabels from './LabwareLabels' import Well from './Well' +import Tip from './Tip' import type {SingleWell} from './Well' import Module from './Module' import type {ModuleType} from './Module' @@ -21,10 +23,12 @@ export { EmptyDeckSlot, Labware, LabwareContainer, + LabwareOutline, LabwareLabels, Module, SlotOverlay, Well, + Tip, } export type { diff --git a/protocol-designer/src/components/SelectablePlate.css b/protocol-designer/src/components/SelectablePlate.css new file mode 100644 index 00000000000..bb12681b854 --- /dev/null +++ b/protocol-designer/src/components/SelectablePlate.css @@ -0,0 +1,6 @@ +@import '@opentrons/components'; + +.tiprack_plate_outline { + fill: var(--c-plate-bg); + stroke: var(--c-charcoal); +} diff --git a/protocol-designer/src/components/SelectablePlate.js b/protocol-designer/src/components/SelectablePlate.js index 1834fa9fa99..c6522c9d8cc 100644 --- a/protocol-designer/src/components/SelectablePlate.js +++ b/protocol-designer/src/components/SelectablePlate.js @@ -1,9 +1,18 @@ // @flow // Wrap Plate with a SelectionRect. import * as React from 'react' +import map from 'lodash/map' +import { + getWellDefsForSVG, + getLabware, + getIsTiprack, +} from '@opentrons/shared-data' import { swatchColors, Labware, + Well, + Tip, + LabwareOutline, LabwareLabels, MIXED_WELL_COLOR, type Channels, @@ -12,6 +21,7 @@ import { import SelectionRect from '../components/SelectionRect.js' import type {ContentsByWell} from '../labware-ingred/types' import type {RectEvent} from '../collision-types' +import styles from './SelectablePlate.css' type LabwareProps = React.ElementProps @@ -21,6 +31,7 @@ export type Props = { containerType: string, selectable?: boolean, + hoverable?: boolean, makeOnMouseOverWell?: (well: string) => (e: SyntheticMouseEvent<*>) => mixed, onMouseExitWell?: (e: SyntheticMouseEvent<*>) => mixed, @@ -45,6 +56,10 @@ function getFillColor (groupIds: Array): ?string { return MIXED_WELL_COLOR } +// TODO: BC 2018-10-08 for disconnect hover and select in the IngredSelectionModal from +// redux, use SelectableLabware or similar component there. Also, where we are using this +// component in LabwareOnDeck, with no hover or select capabilities, pull out implicit highlighting +// labware into it's own component probably near to View Results' BrowsableLabware export default function SelectablePlate (props: Props) { const { wellContents, @@ -52,41 +67,71 @@ export default function SelectablePlate (props: Props) { containerType, onSelectionMove, onSelectionDone, - selectable, + selectable = false, + hoverable = true, makeOnMouseOverWell, onMouseExitWell, } = props - const getWellProps = (wellName) => { - const well = wellContents[wellName] - - return { - onMouseOver: makeOnMouseOverWell && makeOnMouseOverWell(wellName), - onMouseLeave: onMouseExitWell, - selectable, - wellName, - - highlighted: well.highlighted, - selected: well.selected, - error: well.error, - maxVolume: well.maxVolume, - - fillColor: getFillColor(well.groupIds), + // NOTE: LabwareOnDeck is not selectable or hoverable + if (!hoverable && !selectable) { + const allWellDefsByName = getWellDefsForSVG(containerType) + const isTiprack = getIsTiprack(containerType) + const labwareDefinition = getLabware(containerType) + + const tipVolume = labwareDefinition && labwareDefinition.metadata && labwareDefinition.metadata.tipVolume + + return ( + + + {map(wellContents, (well, wellName) => { + if (isTiprack) { + const tipProps = (getTipProps && getTipProps(wellName)) || {} + return ( + + ) + } else { + return ( + + ) + } + })} + + ) + } else { // NOTE: Currently only selectable and hoverable (bound to redux) in IngredientSelectionModal + const getWellProps = (wellName) => { + const well = wellContents[wellName] + return { + onMouseOver: makeOnMouseOverWell && makeOnMouseOverWell(wellName), + onMouseLeave: onMouseExitWell, + selectable, + wellName, + highlighted: well.highlighted, + selected: well.selected, + error: well.error, + maxVolume: well.maxVolume, + fillColor: getFillColor(well.groupIds), + } } - } - - const labwareComponent = - if (!selectable) return labwareComponent // don't wrap labwareComponent with SelectionRect - - return ( - - {labwareComponent} - - - ) + return ( + + + + + ) + } } diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 34b9032ed27..ccd477bdb31 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -66,7 +66,7 @@ function SlotWithLabware (props: SlotWithLabwareProps) { href={labwareImages[containerType]} width={SLOT_WIDTH_MM} height={SLOT_HEIGHT_MM} /> - : + : } @@ -141,75 +141,86 @@ type LabwareOnDeckProps = { setLabwareName: (name: ?string) => mixed, setDefaultLabwareName: () => mixed, } -export default function LabwareOnDeck (props: LabwareOnDeckProps) { - const { - slot, - containerId, - containerName, - containerType, - - showNameOverlay, - slotHasLabware, - highlighted, - - addLabwareMode, - canAddIngreds, - deckSetupMode, - moveLabwareMode, - - addLabware, - editLiquids, - deleteLabware, - - cancelMove, - moveLabwareDestination, - moveLabwareSource, - slotToMoveFrom, - - setDefaultLabwareName, - setLabwareName, - } = props - - // determine what overlay to show - let overlay = null - if (deckSetupMode && !addLabwareMode) { - if (moveLabwareMode) { - overlay = (slotToMoveFrom === slot) - ? - : - } else if (showNameOverlay) { - overlay = +class LabwareOnDeck extends React.Component { + shouldComponentUpdate (nextProps) { + if (nextProps.addLabwareMode || nextProps.moveLabwareMode) { + return true } else { - overlay = (slotHasLabware) - ? - : + return this.props.highlighted !== nextProps.highlighted } } + render () { + const { + slot, + containerId, + containerName, + containerType, + + showNameOverlay, + slotHasLabware, + highlighted, + + addLabwareMode, + canAddIngreds, + deckSetupMode, + moveLabwareMode, + + addLabware, + editLiquids, + deleteLabware, + + cancelMove, + moveLabwareDestination, + moveLabwareSource, + slotToMoveFrom, + + setDefaultLabwareName, + setLabwareName, + } = this.props + + // determine what overlay to show + let overlay = null + if (deckSetupMode && !addLabwareMode) { + if (moveLabwareMode) { + overlay = (slotToMoveFrom === slot) + ? + : + } else if (showNameOverlay) { + overlay = + } else { + overlay = (slotHasLabware) + ? + : + } + } - const labwareOrSlot = (slotHasLabware) - ? - : - - return ( - - {labwareOrSlot} - {overlay} - - ) + const labwareOrSlot = (slotHasLabware) + ? + : + + return ( + + {labwareOrSlot} + {overlay} + + ) + } } + +export default LabwareOnDeck diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index e95bb1cfcc2..17554f011c8 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react' import reduce from 'lodash/reduce' +import map from 'lodash/map' // import {connect} from 'react-redux' import { swatchColors, @@ -8,8 +9,12 @@ import { LabwareLabels, MIXED_WELL_COLOR, type Channels, + LabwareOutline, + Well, } from '@opentrons/components' +import {getWellDefsForSVG} from '@opentrons/shared-data' + import {getCollidingWells} from '../../utils' import {SELECTABLE_WELL_CLASS} from '../../constants' import {getWellSetForMultichannel} from '../../well-selection/utils' @@ -27,7 +32,6 @@ type LabwareProps = React.ElementProps export type Props = { wellContents: ContentsByWell, - getTipProps?: $PropertyType, containerType: string, updateSelectedWells: (Wells) => mixed, @@ -100,31 +104,31 @@ class SelectableLabware extends React.Component { render () { const { wellContents, - getTipProps, containerType, highlightedWells, selectedWells, } = this.props - const getWellProps = (wellName) => { - const well = wellContents[wellName] - - return { - selectable: true, - wellName, - onMouseOver: this.makeHandleMouseOverWell(wellName), - onMouseLeave: this.handleMouseExitWell, - highlighted: Object.keys(highlightedWells).includes(wellName), - selected: Object.keys(selectedWells).includes(wellName), - error: well && well.error, - maxVolume: well && well.maxVolume, - fillColor: getFillColor(well.groupIds), - } - } + const allWellDefsByName = getWellDefsForSVG(containerType) return ( - + + + {map(wellContents, (well, wellName) => ( + + ))} + ) From 9322abcbed1a3e228d2d965d28e3b87262226f73 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 14:50:23 -0400 Subject: [PATCH 06/27] feat(protocol-designer): separate and execute performance audit of labware components In the 4 main places we are using, or will use, the SelectablePlate component, there are shared aspects of the abstraction that weigh down the application in places where the extra information is not needed or used. This branch begins to separate our implementations by first creating a SelectableLabware component which is used in the WellSelectionModal and maintains local state. The LabwareOnDeck is also not rendering with select and hover handlers. The IngredientSelectionModal, will use SelectableLabware or a similar component once the ingredient form sustains upcoming changes. Closes #2285 --- components/src/deck/Well.js | 2 +- .../WellSelectionInput/WellSelectionInput.js | 2 ++ .../WellSelectionInput/WellSelectionModal.js | 18 ++++++------ .../src/components/labware/LabwareOnDeck.js | 2 +- .../components/labware/SelectableLabware.js | 29 +++++++------------ 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/components/src/deck/Well.js b/components/src/deck/Well.js index 6d92f2a03e1..0c66eefa8da 100644 --- a/components/src/deck/Well.js +++ b/components/src/deck/Well.js @@ -25,7 +25,7 @@ type Props = { } class Well extends React.Component { - shouldComponentUpdate (nextProps) { + shouldComponentUpdate (nextProps: Props) { return this.props.highlighted !== nextProps.highlighted || this.props.selected !== nextProps.selected || this.props.fillColor !== nextProps.fillColor diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js index 1e20a1ac489..1903496b450 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js @@ -13,6 +13,8 @@ type Props = { onClick?: (e: SyntheticMouseEvent<*>) => mixed, errorToShow: ?string, isMulti: ?boolean, + pipetteId: ?string, + labwareId: ?string, } type State = {isModalOpen: boolean} diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 46f12528a23..c3f19b77f80 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -16,21 +16,21 @@ import {selectors} from '../../../labware-ingred/reducers' import {selectors as steplistSelectors} from '../../../steplist' import {changeFormInput} from '../../../steplist/actions' import type {StepFieldName} from '../../../steplist/fieldLevel' +import type {PipetteData} from '../../../step-generation/types' +import type {Wells, ContentsByWell} from '../../../labware-ingred/types' import {SelectableLabware} from '../../labware' import SingleLabwareWrapper from '../../SingleLabware' import WellSelectionInstructions from '../../WellSelectionInstructions' -import type {PipetteData} from '../step-generation/types' -import type {Wells, ContentsByWell} from '../labware-ingred/types' import styles from './WellSelectionModal.css' import modalStyles from '../../modals/modal.css' type OP = { - pipetteId: string, - labwareId: string, + pipetteId: ?string, + labwareId: ?string, isOpen: boolean, - onCloseClick: (e: SyntheticEvent<*>) => mixed, + onCloseClick: (e: ?SyntheticEvent<*>) => mixed, name: StepFieldName, } type SP = { @@ -39,7 +39,7 @@ type SP = { wellContents: ContentsByWell, containerType: string, } -type DP = {saveWellSelection: () => mixed} +type DP = {saveWellSelection: (Wells) => mixed} type Props = OP & SP & DP type State = {selectedWells: Wells, highlightedWells: Wells} @@ -114,7 +114,7 @@ class WellSelectionModal extends React.Component { function mapStateToProps (state: BaseState, ownProps: OP): SP { const {pipetteId, labwareId} = ownProps - const labware = selectors.getLabware(state)[labwareId] + const labware = labwareId && selectors.getLabware(state)[labwareId] const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) const stepId = steplistSelectors.getActiveItem(state).id @@ -124,8 +124,8 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { const formData = steplistSelectors.getUnsavedForm(state) return { - initialSelectedWells: formData ? formData[ownProps.name] : null, - pipette: pipetteSelectors.equippedPipettes(state)[pipetteId], + initialSelectedWells: formData ? formData[ownProps.name] : [], + pipette: pipetteId ? pipetteSelectors.equippedPipettes(state)[pipetteId] : null, wellContents: labware ? allWellContentsForSteps[timelineIdx][labware.id] : {}, containerType: labware ? labware.type : 'missing labware', } diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index ccd477bdb31..d087dcb01cd 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -142,7 +142,7 @@ type LabwareOnDeckProps = { setDefaultLabwareName: () => mixed, } class LabwareOnDeck extends React.Component { - shouldComponentUpdate (nextProps) { + shouldComponentUpdate (nextProps: LabwareOnDeckProps) { if (nextProps.addLabwareMode || nextProps.moveLabwareMode) { return true } else { diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 17554f011c8..67c37c154d2 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -2,10 +2,8 @@ import * as React from 'react' import reduce from 'lodash/reduce' import map from 'lodash/map' -// import {connect} from 'react-redux' import { swatchColors, - Labware, LabwareLabels, MIXED_WELL_COLOR, type Channels, @@ -20,23 +18,16 @@ import {SELECTABLE_WELL_CLASS} from '../../constants' import {getWellSetForMultichannel} from '../../well-selection/utils' import SelectionRect from '../SelectionRect.js' import type {ContentsByWell, Wells} from '../../labware-ingred/types' - -// import * as wellContentsSelectors from '../../top-selectors/well-contents' -// import wellSelectionSelectors from '../../well-selection/selectors' -// import {selectors} from '../../labware-ingred/reducers' -// import {selectors as steplistSelectors} from '../../steplist' import type {GenericRect} from '../../collision-types' -// import type {BaseState} from '../../types' - -type LabwareProps = React.ElementProps export type Props = { wellContents: ContentsByWell, containerType: string, - updateSelectedWells: (Wells) => mixed, - - // used by container - containerId: string, + selectedWells: Wells, + highlightedWells: Wells, + selectWells: (Wells) => mixed, + deselectWells: (Wells) => mixed, + updateHighlightedWells: (Wells) => mixed, pipetteChannels?: ?Channels, } @@ -47,7 +38,7 @@ function getFillColor (groupIds: Array): ?string { return MIXED_WELL_COLOR } -class SelectableLabware extends React.Component { +class SelectableLabware extends React.Component { _getWellsFromRect = (rect: GenericRect): * => { const selectedWells = getCollidingWells(rect, SELECTABLE_WELL_CLASS) return this._wellsFromSelected(selectedWells) @@ -73,12 +64,12 @@ class SelectableLabware extends React.Component { return selectedWells } - handleSelectionMove = (e, rect) => { + handleSelectionMove = (e: MouseEvent, rect: GenericRect) => { if (!e.shiftKey) { this.props.updateHighlightedWells(this._getWellsFromRect(rect)) } } - handleSelectionDone = (e, rect) => { + handleSelectionDone = (e: MouseEvent, rect: GenericRect) => { const wells = this._getWellsFromRect(rect) if (e.shiftKey) { this.props.deselectWells(wells) @@ -87,11 +78,11 @@ class SelectableLabware extends React.Component { } } - makeHandleMouseOverWell = (wellName) => () => { + makeHandleMouseOverWell = (wellName: string) => () => { if (this.props.pipetteChannels === 8) { const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) const nextHighlightedWells = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) - this.props.updateHighlightedWells(nextHighlightedWells) + nextHighlightedWells && this.props.updateHighlightedWells(nextHighlightedWells) } else { this.props.updateHighlightedWells({[wellName]: wellName}) } From f5c565ee8360a72b7a4efbc871e5b24fb732acfb Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 16:09:18 -0400 Subject: [PATCH 07/27] fix move labware bug --- protocol-designer/src/components/labware/LabwareOnDeck.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index d087dcb01cd..d072d306903 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -143,7 +143,12 @@ type LabwareOnDeckProps = { } class LabwareOnDeck extends React.Component { shouldComponentUpdate (nextProps: LabwareOnDeckProps) { - if (nextProps.addLabwareMode || nextProps.moveLabwareMode) { + const shouldAlwaysUpdate = this.props.addLabwareMode || + nextProps.addLabwareMode || + this.props.moveLabwareMode || + nextProps.moveLabwareMode + + if (shouldAlwaysUpdate) { return true } else { return this.props.highlighted !== nextProps.highlighted From 2c4214b6919d087803267bb9ae931600ce8dc9d2 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 9 Oct 2018 13:48:20 -0400 Subject: [PATCH 08/27] sneaky flow errors --- .../StepEditForm/WellSelectionInput/WellSelectionModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index c3f19b77f80..c9b26d8213b 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -85,7 +85,7 @@ class WellSelectionModal extends React.Component {
From a1350809d302b6ea95a0ea01f31cbf0bcbe7cd4e Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 9 Oct 2018 16:08:26 -0400 Subject: [PATCH 09/27] wells not selectable --- protocol-designer/src/components/SelectablePlate.js | 1 - 1 file changed, 1 deletion(-) diff --git a/protocol-designer/src/components/SelectablePlate.js b/protocol-designer/src/components/SelectablePlate.js index c6522c9d8cc..6acf152468f 100644 --- a/protocol-designer/src/components/SelectablePlate.js +++ b/protocol-designer/src/components/SelectablePlate.js @@ -98,7 +98,6 @@ export default function SelectablePlate (props: Props) { } else { return ( Date: Tue, 9 Oct 2018 17:50:52 -0400 Subject: [PATCH 10/27] fix bugs that ian uncovered --- .../WellSelectionInput/WellSelectionInput.js | 2 +- .../WellSelectionInput/WellSelectionModal.js | 6 +++--- .../src/components/labware/LabwareOnDeck.js | 6 +++++- .../src/components/labware/SelectableLabware.js | 13 ++++++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js index 1903496b450..d6c5def4228 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js @@ -40,7 +40,7 @@ class WellSelectionInput extends React.Component { error={this.props.errorToShow} /> { + onCloseClick={this.props.onCloseClick}>
{ function mapStateToProps (state: BaseState, ownProps: OP): SP { const {pipetteId, labwareId} = ownProps - const labware = labwareId && selectors.getLabware(state)[labwareId] + const allLabware = selectors.getLabware(state) + const labware = labwareId && allLabware && allLabware[labwareId] const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) const stepId = steplistSelectors.getActiveItem(state).id diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 78496e2db9b..db4fdcc4659 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -145,7 +145,11 @@ class LabwareOnDeck extends React.Component { this.props.moveLabwareMode || nextProps.moveLabwareMode - if (shouldAlwaysUpdate) { + const labwarePresenceChange = (this.props.containerId && !nextProps.containerId) || + (!this.props.containerId && nextProps.containerId) + const nameOverlayChange = (this.props.showNameOverlay && !nextProps.showNameOverlay) || + (!this.props.showNameOverlay && nextProps.showNameOverlay) + if (shouldAlwaysUpdate || labwarePresenceChange || nameOverlayChange) { return true } else { return this.props.highlighted !== nextProps.highlighted diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 67c37c154d2..a47d41bb713 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -53,8 +53,7 @@ class SelectableLabware extends React.Component { const wellSet = getWellSetForMultichannel(this.props.containerType, well) if (!wellSet) return acc - const selectedWellsFromSet = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) - return {...acc, ...selectedWellsFromSet} + return {...acc, [wellSet[0]]: wellSet[0]} }, {}) return primaryWells @@ -98,10 +97,18 @@ class SelectableLabware extends React.Component { containerType, highlightedWells, selectedWells, + pipetteChannels, } = this.props const allWellDefsByName = getWellDefsForSVG(containerType) + const selectedWellSets = pipetteChannels === 8 + ? reduce(selectedWells, (acc, wellName) => { + const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) + if (!wellSet) return acc + return [...acc, ...wellSet] + }, []) + : Object.keys(selectedWells) return ( @@ -114,7 +121,7 @@ class SelectableLabware extends React.Component { onMouseOver={this.makeHandleMouseOverWell(wellName)} onMouseLeave={this.handleMouseExitWell} highlighted={Object.keys(highlightedWells).includes(wellName)} - selected={Object.keys(selectedWells).includes(wellName)} + selected={selectedWellSets.includes(wellName)} fillColor={getFillColor(well.groupIds)} svgOffset={{x: 1, y: -3}} wellDef={allWellDefsByName[wellName]} /> From 967492db410254af416b8df739b328eef0ff44b4 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 10:50:12 -0400 Subject: [PATCH 11/27] add comment --- components/src/deck/Labware.js | 1 + protocol-designer/src/components/labware/LabwareOnDeck.js | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/src/deck/Labware.js b/components/src/deck/Labware.js index 63dbb3141d3..fbb88ef708c 100644 --- a/components/src/deck/Labware.js +++ b/components/src/deck/Labware.js @@ -87,6 +87,7 @@ function createWell ( /> } +// TODO: BC 2018-10-10 this is a class component because it should probably have a sCU for performance reasons class Labware extends React.Component { render () { const {labwareType, getTipProps, getWellProps} = this.props diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index db4fdcc4659..5d51cb60441 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -149,11 +149,8 @@ class LabwareOnDeck extends React.Component { (!this.props.containerId && nextProps.containerId) const nameOverlayChange = (this.props.showNameOverlay && !nextProps.showNameOverlay) || (!this.props.showNameOverlay && nextProps.showNameOverlay) - if (shouldAlwaysUpdate || labwarePresenceChange || nameOverlayChange) { - return true - } else { - return this.props.highlighted !== nextProps.highlighted - } + if (shouldAlwaysUpdate || labwarePresenceChange || nameOverlayChange) return true + return this.props.highlighted !== nextProps.highlighted } render () { const { From 90d33398be8a21e9d578b452c4a6fe9374315281 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 16:06:31 -0400 Subject: [PATCH 12/27] fix up extra setMoveLabwareMode --- .../src/containers/ConnectedDeckSetup.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index 9745d59f3ab..17b5c1952ba 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -22,6 +22,11 @@ type StateProps = { ingredSelectionMode: boolean, } type DispatchProps = {cancelMoveLabwareMode: () => mixed} +type Props = { + deckSetupMode: boolean, + ingredSelectionMode: boolean, + cancelMoveLabwareMode: () => void, +} const mapStateToProps = (state: BaseState): StateProps => ({ deckSetupMode: ( @@ -29,14 +34,23 @@ const mapStateToProps = (state: BaseState): StateProps => ({ ), // TODO SOON remove all uses of the `activeModals` selector ingredSelectionMode: !!ingredSelModIsVisible(selectors.activeModals(state)), + _moveLabwareMode: !!selectors.slotToMoveFrom(state), }) const mapDispatchToProps = (dispatch: ThunkDispatch<*>): DispatchProps => ({ cancelMoveLabwareMode: () => dispatch(actions.setMoveLabwareMode()), }) +const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props => ({ + deckSetupMode: stateProps.deckSetupMode, + ingredSelectionMode: stateProps.ingredSelectionMode, + cancelMoveLabwareMode: () => { + if (stateProps._moveLabwareMode) dispatchProps.cancelMoveLabwareMode() + }, +}) + // TODO Ian 2018-02-16 this will be broken apart and incorporated into ProtocolEditor -class DeckSetup extends React.Component { +class DeckSetup extends React.Component { renderDeck = () => (
@@ -73,4 +87,4 @@ class DeckSetup extends React.Component { } } -export default connect(mapStateToProps, mapDispatchToProps)(DeckSetup) +export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(DeckSetup) From 8c0f9e6d7dc23d0f8bf8bddef23c61898ed5a9d4 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 16:52:11 -0400 Subject: [PATCH 13/27] building out --- .../src/components/labware/BrowseLabwareOverlay.js | 0 .../src/components/labware/LabwareOnDeck.js | 14 ++++++++------ .../src/containers/LabwareContainer.js | 9 +++++---- protocol-designer/src/labware-ingred/actions.js | 7 +++++++ .../src/labware-ingred/reducers/index.js | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 protocol-designer/src/components/labware/BrowseLabwareOverlay.js diff --git a/protocol-designer/src/components/labware/BrowseLabwareOverlay.js b/protocol-designer/src/components/labware/BrowseLabwareOverlay.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 5d51cb60441..4095f96b9f5 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -16,6 +16,8 @@ import ClickableText from './ClickableText' import SelectablePlate from '../../containers/SelectablePlate.js' import NameThisLabwareOverlay from './NameThisLabwareOverlay.js' import DisabledSelectSlotOverlay from './DisabledSelectSlotOverlay.js' +import BrowseLabwareOverlay from './BrowseLabwareOverlay.js' +import {type TerminalItemId, START_TERMINAL_ITEM_ID, END_TERMINAL_ITEM_ID} from '../../steplist' function LabwareDeckSlotOverlay ({ canAddIngreds, @@ -123,7 +125,7 @@ type LabwareOnDeckProps = { addLabwareMode: boolean, canAddIngreds: boolean, - deckSetupMode: boolean, + selectedTerminalItem: ?TerminalItemId, moveLabwareMode: boolean, addLabware: () => mixed, @@ -165,7 +167,7 @@ class LabwareOnDeck extends React.Component { addLabwareMode, canAddIngreds, - deckSetupMode, + selectedTerminalItem, moveLabwareMode, addLabware, @@ -183,7 +185,7 @@ class LabwareOnDeck extends React.Component { // determine what overlay to show let overlay = null - if (deckSetupMode && !addLabwareMode) { + if (selectedTerminalItem === START_TERMINAL_ITEM_ID && !addLabwareMode) { if (moveLabwareMode) { overlay = (slotToMoveFrom === slot) ? { editLiquids, moveLabwareSource, }} /> - : + : } + } else if (selectedTerminalItem === END_TERMINAL_ITEM_ID) { + overlay = } const labwareOrSlot = (slotHasLabware) diff --git a/protocol-designer/src/containers/LabwareContainer.js b/protocol-designer/src/containers/LabwareContainer.js index 81557379dea..528e5bbc1fc 100644 --- a/protocol-designer/src/containers/LabwareContainer.js +++ b/protocol-designer/src/containers/LabwareContainer.js @@ -11,6 +11,7 @@ import { modifyContainer, openAddLabwareModal, + drillDownLabware, setMoveLabwareMode, moveLabware, @@ -54,7 +55,7 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { const selectedContainer = selectors.getSelectedContainer(state) const isSelectedSlot = !!(selectedContainer && selectedContainer.slot === slot) - const deckSetupMode = steplistSelectors.getSelectedTerminalItemId(state) === START_TERMINAL_ITEM_ID + const selectedTerminalItem = steplistSelectors.getSelectedTerminalItemId(state) const labwareHasName = container && selectors.getSavedLabware(state)[containerId] const isTiprack = getIsTiprack(containerType) const showNameOverlay = container && !isTiprack && !labwareHasName @@ -89,13 +90,13 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { showNameOverlay, slotToMoveFrom, - highlighted: (deckSetupMode) + highlighted: selectedTerminalItem === START_TERMINAL_ITEM_ID // in deckSetupMode, labware is highlighted when selected (currently editing ingredients) // or when targeted by an open "Add Labware" modal ? (isSelectedSlot || selectors.selectedAddLabwareSlot(state) === slot) // outside of deckSetupMode, labware is highlighted when step/substep is hovered : steplistSelectors.hoveredStepLabware(state).includes(containerId), - deckSetupMode, + selectedTerminalItem, slot, containerName, @@ -116,7 +117,7 @@ function mergeProps (stateProps: SP, dispatchProps: {dispatch: Dispatch<*>}, own window.confirm(`Are you sure you want to permanently delete ${containerName || containerType} in slot ${slot}?`) && dispatch(deleteContainer({containerId, slot, containerType})) ), - + drillDownLabware: () => dispatch(drillDownLabware(containerId)), cancelMove: () => dispatch(setMoveLabwareMode()), moveLabwareDestination: () => dispatch(moveLabware(slot)), moveLabwareSource: () => dispatch(setMoveLabwareMode(slot)), diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index 98e21e8aca7..abd238b95de 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -39,6 +39,13 @@ export const closeIngredientSelector = createAction( () => {} ) +// ===== Drill Down on Labware ==== + +export const drillDownOnLabware = createAction( + 'DRILL_DOWN_ON_LABWARE', + (labwareId: string) => labwareId +) + // ===== export const editModeIngredientGroup = createAction( diff --git a/protocol-designer/src/labware-ingred/reducers/index.js b/protocol-designer/src/labware-ingred/reducers/index.js index cdc774cbff8..14b045032c3 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.js +++ b/protocol-designer/src/labware-ingred/reducers/index.js @@ -81,6 +81,12 @@ const renameLabwareFormMode = handleActions({ EDIT_MODE_INGREDIENT_GROUP: () => false, }, false) +type DrillDownLabwareId = string | null +const drillDownLabwareId = handleActions({ + DRILL_DOWN_ON_LABWARE: (state, action: ActionType): DrillDownLabwareId => action.payload, + DRILL_UP_FROM_LABWARE: (state, action: ActionType): DrillDownLabwareId => null, +}, null) + type ContainersState = { [id: string]: ?Labware, } @@ -264,6 +270,7 @@ export type RootState = {| modeLabwareSelection: ?DeckSlot, moveLabwareMode: ?DeckSlot, selectedContainerId: SelectedContainerId, + drillDownLabwareId: DrillDownLabwareId, containers: ContainersState, savedLabware: SavedLabwareState, ingredients: IngredientsState, @@ -276,6 +283,7 @@ const rootReducer = combineReducers({ modeLabwareSelection, moveLabwareMode, selectedContainerId, + drillDownLabwareId, containers, savedLabware, ingredients, @@ -379,6 +387,11 @@ const getSelectedContainer: Selector = createSelector( (_selectedId, _labware) => (_selectedId && _labware[_selectedId]) || null ) +const getDrillDownLabwareId: Selector = createSelector( + rootSelector, + rootState => rootState.drillDownLabwareId +) + type ContainersBySlot = { [DeckSlot]: {...Labware, containerId: string} } const containersBySlot: Selector = createSelector( @@ -461,6 +474,7 @@ export const selectors = { getSavedLabware, getSelectedContainer, getSelectedContainerId, + getDrillDownLabwareId, activeModals, getRenameLabwareFormMode, From a0188fd1a5a73c701d1a8d8534430ec83173563f Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 8 Oct 2018 18:04:25 -0400 Subject: [PATCH 14/27] debug browse labware modal on render --- components/src/utils.js | 11 ++ .../src/components/SelectablePlate.js | 20 +-- .../components/labware/BrowseLabwareModal.js | 131 ++++++++++++++++++ .../labware/BrowseLabwareOverlay.js | 24 ++++ .../src/components/labware/LabwareOnDeck.js | 8 +- .../components/labware/SelectableLabware.js | 12 +- .../src/containers/ConnectedDeckSetup.js | 11 +- .../src/containers/LabwareContainer.js | 6 +- .../src/labware-ingred/actions.js | 5 + 9 files changed, 196 insertions(+), 32 deletions(-) create mode 100644 protocol-designer/src/components/labware/BrowseLabwareModal.js diff --git a/components/src/utils.js b/components/src/utils.js index d1e8ee369f1..78baa5c3498 100644 --- a/components/src/utils.js +++ b/components/src/utils.js @@ -1,5 +1,9 @@ // @flow import startCase from 'lodash/startCase' +import { + swatchColors, + MIXED_WELL_COLOR, +} from '@opentrons/components' export const humanizeLabwareType = startCase @@ -21,3 +25,10 @@ export const wellNameSplit = (wellName: string): [string, string] => { return [letters, numbers] } + +// TODO Ian 2018-07-20: make sure '__air__' or other pseudo-ingredients don't get in here +export const ingredIdsToColor = (groupIds: Array): ?string => { + if (groupIds.length === 0) return null + if (groupIds.length === 1) return swatchColors(Number(groupIds[0])) + return MIXED_WELL_COLOR +} diff --git a/protocol-designer/src/components/SelectablePlate.js b/protocol-designer/src/components/SelectablePlate.js index 6acf152468f..05041044ba6 100644 --- a/protocol-designer/src/components/SelectablePlate.js +++ b/protocol-designer/src/components/SelectablePlate.js @@ -8,13 +8,12 @@ import { getIsTiprack, } from '@opentrons/shared-data' import { - swatchColors, Labware, Well, Tip, LabwareOutline, LabwareLabels, - MIXED_WELL_COLOR, + ingredIdsToColor, type Channels, } from '@opentrons/components' @@ -43,19 +42,6 @@ export type Props = { pipetteChannels?: ?Channels, } -// TODO Ian 2018-07-20: make sure '__air__' or other pseudo-ingredients don't get in here -function getFillColor (groupIds: Array): ?string { - if (groupIds.length === 0) { - return null - } - - if (groupIds.length === 1) { - return swatchColors(Number(groupIds[0])) - } - - return MIXED_WELL_COLOR -} - // TODO: BC 2018-10-08 for disconnect hover and select in the IngredSelectionModal from // redux, use SelectableLabware or similar component there. Also, where we are using this // component in LabwareOnDeck, with no hover or select capabilities, pull out implicit highlighting @@ -102,7 +88,7 @@ export default function SelectablePlate (props: Props) { wellName={wellName} highlighted={well.highlighted} selected={well.selected} - fillColor={getFillColor(well.groupIds)} + fillColor={ingredIdsToColor(well.groupIds)} svgOffset={{x: 1, y: -3}} wellDef={allWellDefsByName[wellName]} /> ) @@ -122,7 +108,7 @@ export default function SelectablePlate (props: Props) { selected: well.selected, error: well.error, maxVolume: well.maxVolume, - fillColor: getFillColor(well.groupIds), + fillColor: ingredIdsToColor(well.groupIds), } } diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js new file mode 100644 index 00000000000..3919f526610 --- /dev/null +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -0,0 +1,131 @@ +// @flow +import * as React from 'react' +import cx from 'classnames' +import reduce from 'lodash/reduce' +import map from 'lodash/map' +import {connect} from 'react-redux' + +import { + getWellDefsForSVG, + getLabware, + getIsTiprack, +} from '@opentrons/shared-data' + +import { + Modal, + OutlineButton, + Tip, + Well, + LabwareOutline, + ingredIdsToColor, +} from '@opentrons/components' +import type {BaseState, ThunkDispatch} from '../../types' + +import * as wellContentsSelectors from '../../top-selectors/well-contents' +import {selectors} from '../../labware-ingred/reducers' +import * as labwareIngredsActions from '../../labware-ingred/actions' +import {selectors as steplistSelectors} from '../../steplist' +import type {ContentsByWell} from '../../labware-ingred/types' + +import SingleLabwareWrapper from '../SingleLabware' +import WellSelectionInstructions from '../WellSelectionInstructions' + +import styles from '../StepEditForm/WellSelectionInput/WellSelectionModal.css' +import modalStyles from '../modals/modal.css' + +type OP = { + labwareId: ?string, + drillUp: () => mixed, +} +type SP = { + wellContents: ContentsByWell, + labwareType: string, +} + +type Props = OP & SP + +class BrowseLabwareModal extends React.Component { + constructor (props) { + super(props) + const initialSelectedWells = reduce(this.props.initialSelectedWells, (acc, well) => ( + {...acc, [well]: well} + ), {}) + this.state = {selectedWells: initialSelectedWells, highlightedWells: {}} + } + + handleClose = () => { + this.props.drillUp() + } + + render () { + const allWellDefsByName = getWellDefsForSVG(this.props.labwareType) + const isTiprack = getIsTiprack(this.props.labwareType) + const labwareDefinition = getLabware(this.props.labwareType) + + const tipVolume = labwareDefinition && labwareDefinition.metadata && labwareDefinition.metadata.tipVolume + + return ( + + + + + {map(this.props.wellContents, (well, wellName) => { + if (isTiprack) { + const tipProps = (this.props.getTipProps && this.props.getTipProps(wellName)) || {} + return ( + + ) + } else { + return ( + + ) + } + })} + + + + + ) + } +} + +function mapStateToProps (state: BaseState, ownProps: OP): SP { + const labwareId = selectors.getDrillDownLabwareId(state) + + const labware = labwareId && selectors.getLabware(state)[labwareId] + const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) + + const stepId = steplistSelectors.getActiveItem(state).id + // TODO: Ian 2018-07-31 replace with util function, "findIndexOrNull"? + const orderedSteps = steplistSelectors.orderedSteps(state) + const timelineIdx = orderedSteps.findIndex(id => id === stepId) + + return { + wellContents: labware ? allWellContentsForSteps[timelineIdx][labware.id] : {}, + labwareType: labware ? labware.type : 'missing labware', + } +} + +function mapDispatchToProps (dispatch: ThunkDispatch<*>, ownProps: OP): DP { + return { + drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(BrowseLabwareModal) diff --git a/protocol-designer/src/components/labware/BrowseLabwareOverlay.js b/protocol-designer/src/components/labware/BrowseLabwareOverlay.js index e69de29bb2d..a31a1af2a28 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareOverlay.js +++ b/protocol-designer/src/components/labware/BrowseLabwareOverlay.js @@ -0,0 +1,24 @@ +// @flow +import React from 'react' +import cx from 'classnames' +import styles from './labware.css' + +import ClickableText from './ClickableText' + +type Props = { + drillDown: () => mixed, + drillUp: () => mixed, +} + +function BrowseLabwareOverlay (props: Props) { + return ( + + + + + ) +} + +export default BrowseLabwareOverlay diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 4095f96b9f5..2ea4abe836f 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -130,6 +130,8 @@ type LabwareOnDeckProps = { addLabware: () => mixed, editLiquids: () => mixed, + drillDown: () => mixed, + drillUp: () => mixed, deleteLabware: () => mixed, cancelMove: () => mixed, @@ -140,6 +142,8 @@ type LabwareOnDeckProps = { setLabwareName: (name: ?string) => mixed, setDefaultLabwareName: () => mixed, } + +// TODO: BC 2018-10-08 move these connections to lower lever components class LabwareOnDeck extends React.Component { shouldComponentUpdate (nextProps: LabwareOnDeckProps) { const shouldAlwaysUpdate = this.props.addLabwareMode || @@ -169,6 +173,8 @@ class LabwareOnDeck extends React.Component { canAddIngreds, selectedTerminalItem, moveLabwareMode, + drillDown, + drillUp, addLabware, editLiquids, @@ -209,7 +215,7 @@ class LabwareOnDeck extends React.Component { : } } else if (selectedTerminalItem === END_TERMINAL_ITEM_ID) { - overlay = + overlay = } const labwareOrSlot = (slotHasLabware) diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index a47d41bb713..cf9f5c55f58 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -3,12 +3,11 @@ import * as React from 'react' import reduce from 'lodash/reduce' import map from 'lodash/map' import { - swatchColors, LabwareLabels, - MIXED_WELL_COLOR, type Channels, LabwareOutline, Well, + ingredIdsToColor, } from '@opentrons/components' import {getWellDefsForSVG} from '@opentrons/shared-data' @@ -31,13 +30,6 @@ export type Props = { pipetteChannels?: ?Channels, } -// TODO Ian 2018-07-20: make sure '__air__' or other pseudo-ingredients don't get in here -function getFillColor (groupIds: Array): ?string { - if (groupIds.length === 0) return null - if (groupIds.length === 1) return swatchColors(Number(groupIds[0])) - return MIXED_WELL_COLOR -} - class SelectableLabware extends React.Component { _getWellsFromRect = (rect: GenericRect): * => { const selectedWells = getCollidingWells(rect, SELECTABLE_WELL_CLASS) @@ -122,7 +114,7 @@ class SelectableLabware extends React.Component { onMouseLeave={this.handleMouseExitWell} highlighted={Object.keys(highlightedWells).includes(wellName)} selected={selectedWellSets.includes(wellName)} - fillColor={getFillColor(well.groupIds)} + fillColor={ingredIdsToColor(well.groupIds)} svgOffset={{x: 1, y: -3}} wellDef={allWellDefsByName[wellName]} /> ))} diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index 17b5c1952ba..92115af8fcb 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -8,10 +8,12 @@ import styles from './Deck.css' import IngredientSelectionModal from '../components/IngredientSelectionModal.js' import LabwareContainer from '../containers/LabwareContainer.js' import LabwareSelectionModal from '../components/LabwareSelectionModal' +import BrowseLabwareModal from '../components/labware/BrowseLabwareModal' import {selectors} from '../labware-ingred/reducers' import * as actions from '../labware-ingred/actions' import {selectors as steplistSelectors, START_TERMINAL_ITEM_ID} from '../steplist' + import type {BaseState, ThunkDispatch} from '../types' const ingredSelModIsVisible = activeModals => activeModals.ingredientSelection && activeModals.ingredientSelection.slot @@ -34,18 +36,22 @@ const mapStateToProps = (state: BaseState): StateProps => ({ ), // TODO SOON remove all uses of the `activeModals` selector ingredSelectionMode: !!ingredSelModIsVisible(selectors.activeModals(state)), + drilledDown: !!selectors.getDrillDownLabwareId(state), _moveLabwareMode: !!selectors.slotToMoveFrom(state), }) const mapDispatchToProps = (dispatch: ThunkDispatch<*>): DispatchProps => ({ cancelMoveLabwareMode: () => dispatch(actions.setMoveLabwareMode()), + drillUpFromLabware: () => dispatch(actions.drillUpFromLabware()), }) const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props => ({ deckSetupMode: stateProps.deckSetupMode, ingredSelectionMode: stateProps.ingredSelectionMode, - cancelMoveLabwareMode: () => { + drilledDown: stateProps.drilledDown, + handleClickOutside: () => { if (stateProps._moveLabwareMode) dispatchProps.cancelMoveLabwareMode() + if (stateProps.drilledDown) dispatchProps.drillUpFromLabware() }, }) @@ -53,7 +59,8 @@ const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props class DeckSetup extends React.Component { renderDeck = () => (
- + {this.props.drilledDown && } + {({ref}) => (
diff --git a/protocol-designer/src/containers/LabwareContainer.js b/protocol-designer/src/containers/LabwareContainer.js index 528e5bbc1fc..9d1fc562289 100644 --- a/protocol-designer/src/containers/LabwareContainer.js +++ b/protocol-designer/src/containers/LabwareContainer.js @@ -11,7 +11,8 @@ import { modifyContainer, openAddLabwareModal, - drillDownLabware, + drillDownOnLabware, + drillUpFromLabware, setMoveLabwareMode, moveLabware, @@ -117,7 +118,8 @@ function mergeProps (stateProps: SP, dispatchProps: {dispatch: Dispatch<*>}, own window.confirm(`Are you sure you want to permanently delete ${containerName || containerType} in slot ${slot}?`) && dispatch(deleteContainer({containerId, slot, containerType})) ), - drillDownLabware: () => dispatch(drillDownLabware(containerId)), + drillDown: () => dispatch(drillDownOnLabware(containerId)), + drillUp: () => dispatch(drillUpFromLabware()), cancelMove: () => dispatch(setMoveLabwareMode()), moveLabwareDestination: () => dispatch(moveLabware(slot)), moveLabwareSource: () => dispatch(setMoveLabwareMode(slot)), diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index abd238b95de..f3408968246 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -46,6 +46,11 @@ export const drillDownOnLabware = createAction( (labwareId: string) => labwareId ) +export const drillUpFromLabware = createAction( + 'DRILL_UP_FROM_LABWARE', + () => {} +) + // ===== export const editModeIngredientGroup = createAction( From fbd80bce693e3326b7e47b7393037fede4e86503 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 9 Oct 2018 15:23:56 -0400 Subject: [PATCH 15/27] feat(protocol-designer): create view to browse final liquid state Build the ability to drill down into a labware from the final deck state in order to inspect the resulting liquid state of your protocol. Closes #2335 --- components/src/deck/LabwareLabels.css | 4 + components/src/deck/LabwareLabels.js | 25 +++-- .../src/components/SingleLabware.js | 14 ++- .../components/labware/BrowseLabwareModal.js | 101 ++++++------------ .../labware/BrowseLabwareOverlay.js | 2 +- .../src/components/labware/LabwareOnDeck.js | 4 +- .../src/components/labware/labware.css | 8 ++ .../src/containers/ConnectedDeckSetup.js | 10 +- .../src/containers/LabwareContainer.js | 8 +- .../src/localization/en/modal.json | 3 + 10 files changed, 91 insertions(+), 88 deletions(-) diff --git a/components/src/deck/LabwareLabels.css b/components/src/deck/LabwareLabels.css index 8f0a0808328..d4d3da1ac73 100644 --- a/components/src/deck/LabwareLabels.css +++ b/components/src/deck/LabwareLabels.css @@ -15,3 +15,7 @@ .tiny_labels:nth-child(odd) { fill: gray; } + +.plate_label.inverted { + fill: var(--c-white); +} diff --git a/components/src/deck/LabwareLabels.js b/components/src/deck/LabwareLabels.js index 2cfc08853d3..943a3630825 100644 --- a/components/src/deck/LabwareLabels.js +++ b/components/src/deck/LabwareLabels.js @@ -8,14 +8,17 @@ import styles from './LabwareLabels.css' type Props = { labwareType: string, + inner?: boolean, } // TODO: Ian 2018-08-14 change these offsets to negative numbers to place outside of Labware -const ROW_OFFSET = 4 -const COLUMN_OFFSET = 4 +const INNER_ROW_OFFSET = 4 +const INNER_COLUMN_OFFSET = 4 +const OUTER_ROW_OFFSET = -4 +const OUTER_COLUMN_OFFSET = -4 export default function LabwareLabels (props: Props) { - const {labwareType} = props + const {labwareType, inner = true} = props // TODO: Ian 2018-06-27 Labels are not aligned nicely, but in new designs they're // supposed to be moved outside of the Plate anyway const allWells = getWellDefsForSVG(labwareType) @@ -39,10 +42,12 @@ export default function LabwareLabels (props: Props) { : 0 return ( 8})} - > + className={cx(styles.plate_label, { + [styles.tiny_labels]: rowLetters.length > 8, + [styles.inverted]: !inner, + })}> {letter} ) @@ -56,9 +61,11 @@ export default function LabwareLabels (props: Props) { return ( 12})} - > + y={inner ? INNER_COLUMN_OFFSET : OUTER_COLUMN_OFFSET} + className={cx(styles.plate_label, { + [styles.tiny_labels]: colNumbers.length > 12, + [styles.inverted]: !inner, + })}> {number} ) diff --git a/protocol-designer/src/components/SingleLabware.js b/protocol-designer/src/components/SingleLabware.js index 2b2dbe1d4e0..a568d7603d5 100644 --- a/protocol-designer/src/components/SingleLabware.js +++ b/protocol-designer/src/components/SingleLabware.js @@ -7,14 +7,22 @@ import styles from './SingleLabware.css' type Props = { className?: string, children?: React.Node, + innerLabels?: boolean, } +const OUTER_LABEL_OFFSET = 8 + /** Simply wraps SVG components like Plate/SelectablePlate with correct dimensions */ export default function SingleLabware (props: Props) { + const {children, className, innerLabels = true} = props + const minX = innerLabels ? 0 : -OUTER_LABEL_OFFSET + const minY = innerLabels ? 0 : -OUTER_LABEL_OFFSET + const width = innerLabels ? SLOT_WIDTH_MM : (SLOT_WIDTH_MM + OUTER_LABEL_OFFSET) + const height = innerLabels ? SLOT_HEIGHT_MM : (SLOT_HEIGHT_MM + OUTER_LABEL_OFFSET) return ( -
- - {props.children} +
+ + {children}
) diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 3919f526610..1644bff1c80 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -1,131 +1,90 @@ // @flow import * as React from 'react' import cx from 'classnames' -import reduce from 'lodash/reduce' import map from 'lodash/map' import {connect} from 'react-redux' -import { - getWellDefsForSVG, - getLabware, - getIsTiprack, -} from '@opentrons/shared-data' +import {getWellDefsForSVG} from '@opentrons/shared-data' import { Modal, - OutlineButton, - Tip, Well, LabwareOutline, + LabwareLabels, ingredIdsToColor, } from '@opentrons/components' import type {BaseState, ThunkDispatch} from '../../types' +import i18n from '../../localization' import * as wellContentsSelectors from '../../top-selectors/well-contents' import {selectors} from '../../labware-ingred/reducers' import * as labwareIngredsActions from '../../labware-ingred/actions' -import {selectors as steplistSelectors} from '../../steplist' import type {ContentsByWell} from '../../labware-ingred/types' import SingleLabwareWrapper from '../SingleLabware' -import WellSelectionInstructions from '../WellSelectionInstructions' -import styles from '../StepEditForm/WellSelectionInput/WellSelectionModal.css' import modalStyles from '../modals/modal.css' +import styles from './labware.css' -type OP = { - labwareId: ?string, - drillUp: () => mixed, -} type SP = { wellContents: ContentsByWell, labwareType: string, } +type DP = { + drillUp: () => mixed, +} -type Props = OP & SP - -class BrowseLabwareModal extends React.Component { - constructor (props) { - super(props) - const initialSelectedWells = reduce(this.props.initialSelectedWells, (acc, well) => ( - {...acc, [well]: well} - ), {}) - this.state = {selectedWells: initialSelectedWells, highlightedWells: {}} - } +type Props = SP & DP +class BrowseLabwareModal extends React.Component { handleClose = () => { this.props.drillUp() } render () { const allWellDefsByName = getWellDefsForSVG(this.props.labwareType) - const isTiprack = getIsTiprack(this.props.labwareType) - const labwareDefinition = getLabware(this.props.labwareType) - - const tipVolume = labwareDefinition && labwareDefinition.metadata && labwareDefinition.metadata.tipVolume return ( - + - - {map(this.props.wellContents, (well, wellName) => { - if (isTiprack) { - const tipProps = (this.props.getTipProps && this.props.getTipProps(wellName)) || {} - return ( - - ) - } else { - return ( - - ) - } - })} + + {map(this.props.wellContents, (well, wellName) => ( + + ))} + - +
{i18n.t('modal.browse_labware.instructions')}
) } } -function mapStateToProps (state: BaseState, ownProps: OP): SP { +function mapStateToProps (state: BaseState): SP { const labwareId = selectors.getDrillDownLabwareId(state) - - const labware = labwareId && selectors.getLabware(state)[labwareId] - const allWellContentsForSteps = wellContentsSelectors.allWellContentsForSteps(state) - - const stepId = steplistSelectors.getActiveItem(state).id - // TODO: Ian 2018-07-31 replace with util function, "findIndexOrNull"? - const orderedSteps = steplistSelectors.orderedSteps(state) - const timelineIdx = orderedSteps.findIndex(id => id === stepId) + const labware = labwareId ? selectors.getLabware(state)[labwareId] : null + const wellContents = labwareId ? wellContentsSelectors.lastValidWellContents(state)[labwareId] : {} return { - wellContents: labware ? allWellContentsForSteps[timelineIdx][labware.id] : {}, + wellContents, labwareType: labware ? labware.type : 'missing labware', } } -function mapDispatchToProps (dispatch: ThunkDispatch<*>, ownProps: OP): DP { - return { - drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware()), - } +function mapDispatchToProps (dispatch: ThunkDispatch<*>): DP { + return {drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware())} } export default connect(mapStateToProps, mapDispatchToProps)(BrowseLabwareModal) diff --git a/protocol-designer/src/components/labware/BrowseLabwareOverlay.js b/protocol-designer/src/components/labware/BrowseLabwareOverlay.js index a31a1af2a28..1dc8ab105f7 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareOverlay.js +++ b/protocol-designer/src/components/labware/BrowseLabwareOverlay.js @@ -16,7 +16,7 @@ function BrowseLabwareOverlay (props: Props) { + iconName='water' y='40%' text='View Liquids' /> ) } diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 2ea4abe836f..e0aa7402895 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -125,6 +125,7 @@ type LabwareOnDeckProps = { addLabwareMode: boolean, canAddIngreds: boolean, + isTiprack: boolean, selectedTerminalItem: ?TerminalItemId, moveLabwareMode: boolean, @@ -171,6 +172,7 @@ class LabwareOnDeck extends React.Component { addLabwareMode, canAddIngreds, + isTiprack, selectedTerminalItem, moveLabwareMode, drillDown, @@ -214,7 +216,7 @@ class LabwareOnDeck extends React.Component { }} /> : } - } else if (selectedTerminalItem === END_TERMINAL_ITEM_ID) { + } else if (selectedTerminalItem === END_TERMINAL_ITEM_ID && slotHasLabware && !isTiprack) { overlay = } diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index 421c2951888..10f426747ed 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -73,3 +73,11 @@ color: var(--c-font-dark); font-size: var(--fs-body-1); } + +.modal_instructions { + user-select: none; + text-align: center; + font-size: 1.25rem; + font-weight: var(--fw-semibold); + color: var(--c-white); +} diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index 92115af8fcb..99eebf334f5 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -22,12 +22,18 @@ const DECK_HEADER = 'Tell the robot where labware and liquids start on the deck' type StateProps = { deckSetupMode: boolean, ingredSelectionMode: boolean, + drilledDown: boolean, + _moveLabwareMode: boolean, +} +type DispatchProps = { + cancelMoveLabwareMode: () => mixed, + drillUpFromLabware: () => mixed, } -type DispatchProps = {cancelMoveLabwareMode: () => mixed} type Props = { deckSetupMode: boolean, + drilledDown: boolean, ingredSelectionMode: boolean, - cancelMoveLabwareMode: () => void, + handleClickOutside: () => void, } const mapStateToProps = (state: BaseState): StateProps => ({ diff --git a/protocol-designer/src/containers/LabwareContainer.js b/protocol-designer/src/containers/LabwareContainer.js index 9d1fc562289..db7bb927108 100644 --- a/protocol-designer/src/containers/LabwareContainer.js +++ b/protocol-designer/src/containers/LabwareContainer.js @@ -42,7 +42,12 @@ type DP = { setDefaultLabwareName: () => mixed, } -type SP = $Diff +type MP = { + drillDown: () => mixed, + drillUp: () => mixed, +} + +type SP = $Diff function mapStateToProps (state: BaseState, ownProps: OP): SP { const {slot} = ownProps @@ -87,6 +92,7 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { moveLabwareMode, setDefaultLabwareName, canAddIngreds, + isTiprack, labwareInfo, showNameOverlay, diff --git a/protocol-designer/src/localization/en/modal.json b/protocol-designer/src/localization/en/modal.json index 36287455094..8fee92e2e09 100644 --- a/protocol-designer/src/localization/en/modal.json +++ b/protocol-designer/src/localization/en/modal.json @@ -1,4 +1,7 @@ { + "browse_labware": { + "instructions": "Hover on a well to see contents" + }, "tip_position": { "title": "Tip Positioning", "body": "Change from where in the well the robot aspirates", From 33317bb387b5ef124fc555d2b5ed9a51749027fc Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 11:39:35 -0400 Subject: [PATCH 16/27] update labels always --- components/src/deck/LabwareLabels.css | 7 ++-- components/src/deck/LabwareLabels.js | 35 +++++-------------- .../src/components/SingleLabware.js | 19 ++++------ .../components/labware/BrowseLabwareModal.js | 2 +- .../src/containers/ConnectedDeckSetup.js | 24 +++++++------ protocol-designer/src/containers/Deck.css | 2 +- 6 files changed, 33 insertions(+), 56 deletions(-) diff --git a/components/src/deck/LabwareLabels.css b/components/src/deck/LabwareLabels.css index d4d3da1ac73..a720766f43a 100644 --- a/components/src/deck/LabwareLabels.css +++ b/components/src/deck/LabwareLabels.css @@ -5,6 +5,7 @@ font-size: var(--fs-tiny); text-anchor: middle; dominant-baseline: middle; + fill: var(--c-white); } .tiny_labels { @@ -13,9 +14,5 @@ } .tiny_labels:nth-child(odd) { - fill: gray; -} - -.plate_label.inverted { - fill: var(--c-white); + fill: var(--c-light-gray); } diff --git a/components/src/deck/LabwareLabels.js b/components/src/deck/LabwareLabels.js index 943a3630825..588f49ccd61 100644 --- a/components/src/deck/LabwareLabels.js +++ b/components/src/deck/LabwareLabels.js @@ -6,21 +6,12 @@ import {getWellDefsForSVG, wellIsRect} from '@opentrons/shared-data' import {wellNameSplit} from '../utils' import styles from './LabwareLabels.css' -type Props = { - labwareType: string, - inner?: boolean, -} - -// TODO: Ian 2018-08-14 change these offsets to negative numbers to place outside of Labware -const INNER_ROW_OFFSET = 4 -const INNER_COLUMN_OFFSET = 4 -const OUTER_ROW_OFFSET = -4 -const OUTER_COLUMN_OFFSET = -4 +type Props = {labwareType: string} +const ROW_OFFSET = -4 +const COLUMN_OFFSET = -4 export default function LabwareLabels (props: Props) { - const {labwareType, inner = true} = props - // TODO: Ian 2018-06-27 Labels are not aligned nicely, but in new designs they're - // supposed to be moved outside of the Plate anyway + const {labwareType} = props const allWells = getWellDefsForSVG(labwareType) if (!allWells) { @@ -37,17 +28,12 @@ export default function LabwareLabels (props: Props) { const rowLabels = rowLetters.map(letter => { const relativeWell = allWells[letter + '1'] - const rectOffset = wellIsRect(relativeWell) - ? relativeWell.length / 2 - : 0 + const rectOffset = wellIsRect(relativeWell) ? relativeWell.length / 2 : 0 return ( 8, - [styles.inverted]: !inner, - })}> + className={cx(styles.plate_label, {[styles.tiny_labels]: rowLetters.length > 8})}> {letter} ) @@ -61,11 +47,8 @@ export default function LabwareLabels (props: Props) { return ( 12, - [styles.inverted]: !inner, - })}> + y={COLUMN_OFFSET} + className={cx(styles.plate_label, {[styles.tiny_labels]: colNumbers.length > 12})}> {number} ) diff --git a/protocol-designer/src/components/SingleLabware.js b/protocol-designer/src/components/SingleLabware.js index a568d7603d5..7cb5c3fdedc 100644 --- a/protocol-designer/src/components/SingleLabware.js +++ b/protocol-designer/src/components/SingleLabware.js @@ -4,21 +4,16 @@ import cx from 'classnames' import {SLOT_WIDTH_MM, SLOT_HEIGHT_MM} from '../constants.js' import styles from './SingleLabware.css' -type Props = { - className?: string, - children?: React.Node, - innerLabels?: boolean, -} - -const OUTER_LABEL_OFFSET = 8 +type Props = {className?: string, children?: React.Node} +const LABEL_OFFSET = 8 /** Simply wraps SVG components like Plate/SelectablePlate with correct dimensions */ export default function SingleLabware (props: Props) { - const {children, className, innerLabels = true} = props - const minX = innerLabels ? 0 : -OUTER_LABEL_OFFSET - const minY = innerLabels ? 0 : -OUTER_LABEL_OFFSET - const width = innerLabels ? SLOT_WIDTH_MM : (SLOT_WIDTH_MM + OUTER_LABEL_OFFSET) - const height = innerLabels ? SLOT_HEIGHT_MM : (SLOT_HEIGHT_MM + OUTER_LABEL_OFFSET) + const {children, className} = props + const minX = -LABEL_OFFSET + const minY = -LABEL_OFFSET + const width = SLOT_WIDTH_MM + LABEL_OFFSET + const height = SLOT_HEIGHT_MM + LABEL_OFFSET return (
diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 1644bff1c80..5ef9af30369 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -49,7 +49,7 @@ class BrowseLabwareModal extends React.Component { className={modalStyles.modal} contentsClassName={cx(modalStyles.modal_contents, modalStyles.transparent_content)} onCloseClick={this.handleClose}> - + {map(this.props.wellContents, (well, wellName) => ( diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index 99eebf334f5..e772634981d 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -64,16 +64,19 @@ const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props // TODO Ian 2018-02-16 this will be broken apart and incorporated into ProtocolEditor class DeckSetup extends React.Component { renderDeck = () => ( -
- {this.props.drilledDown && } - - {({ref}) => ( -
- -
- )} -
-
+ +
{DECK_HEADER}
+
+ {this.props.drilledDown && } + + {({ref}) => ( +
+ +
+ )} +
+
+
) render () { @@ -93,7 +96,6 @@ class DeckSetup extends React.Component { {this.props.ingredSelectionMode && } -
{DECK_HEADER}
{this.renderDeck()}
) diff --git a/protocol-designer/src/containers/Deck.css b/protocol-designer/src/containers/Deck.css index 85c2429e44d..48a011e2f7c 100644 --- a/protocol-designer/src/containers/Deck.css +++ b/protocol-designer/src/containers/Deck.css @@ -10,7 +10,7 @@ @apply --font-header-dark; text-align: center; - padding-top: 1rem; + padding: 1rem 0; } .deck_row { From 87882963b96feb96dd881c85063485e8a3f5f6a9 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 11:45:57 -0400 Subject: [PATCH 17/27] protect browseable modal --- .../src/components/labware/BrowseLabwareModal.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 5ef9af30369..3bc81d5846c 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -74,8 +74,10 @@ class BrowseLabwareModal extends React.Component { function mapStateToProps (state: BaseState): SP { const labwareId = selectors.getDrillDownLabwareId(state) - const labware = labwareId ? selectors.getLabware(state)[labwareId] : null - const wellContents = labwareId ? wellContentsSelectors.lastValidWellContents(state)[labwareId] : {} + const allLabware = selectors.getLabware(state) + const labware = labwareId && allLabware ? allLabware[labwareId] : null + const allWellContents = wellContentsSelectors.lastValidWellContents(state) + const wellContents = labwareId && allWellContents ? allWellContents[labwareId] : {} return { wellContents, From ce2fff7eeed7ce0cc7fed51be12151fe06c0049b Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 11:58:43 -0400 Subject: [PATCH 18/27] save game --- protocol-designer/src/containers/ConnectedDeckSetup.js | 4 ++-- .../src/localization/en/{labware_overlays.json => deck.json} | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) rename protocol-designer/src/localization/en/{labware_overlays.json => deck.json} (84%) diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index e772634981d..dbb0619b046 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -17,7 +17,6 @@ import {selectors as steplistSelectors, START_TERMINAL_ITEM_ID} from '../steplis import type {BaseState, ThunkDispatch} from '../types' const ingredSelModIsVisible = activeModals => activeModals.ingredientSelection && activeModals.ingredientSelection.slot -const DECK_HEADER = 'Tell the robot where labware and liquids start on the deck' type StateProps = { deckSetupMode: boolean, @@ -31,6 +30,7 @@ type DispatchProps = { } type Props = { deckSetupMode: boolean, + header: string, drilledDown: boolean, ingredSelectionMode: boolean, handleClickOutside: () => void, @@ -65,7 +65,7 @@ const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props class DeckSetup extends React.Component { renderDeck = () => ( -
{DECK_HEADER}
+
{this.props.header}
{this.props.drilledDown && } diff --git a/protocol-designer/src/localization/en/labware_overlays.json b/protocol-designer/src/localization/en/deck.json similarity index 84% rename from protocol-designer/src/localization/en/labware_overlays.json rename to protocol-designer/src/localization/en/deck.json index 9de06124fde..f2545f8e9bf 100644 --- a/protocol-designer/src/localization/en/labware_overlays.json +++ b/protocol-designer/src/localization/en/deck.json @@ -1,5 +1,4 @@ -{ - "name_labware": { +"name_labware": { "nickname_placeholder": "Add a nickname?", "add_liquids": "Add Liquids", "leave_empty": "Leave Empty" From e1e322e15bff729a3d6473aa3f942f2805efa90e Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 14:22:29 -0400 Subject: [PATCH 19/27] trouble shooting offset rect --- .../src/components/SelectionRect.js | 1 + .../src/components/SingleLabware.js | 2 +- .../labware/NameThisLabwareOverlay.js | 6 +-- .../src/containers/ConnectedDeckSetup.js | 52 +++++++++++-------- .../src/localization/en/deck.json | 15 ++++-- .../src/localization/en/index.js | 4 +- 6 files changed, 48 insertions(+), 32 deletions(-) diff --git a/protocol-designer/src/components/SelectionRect.js b/protocol-designer/src/components/SelectionRect.js index 89938166989..7c66946b1a9 100644 --- a/protocol-designer/src/components/SelectionRect.js +++ b/protocol-designer/src/components/SelectionRect.js @@ -43,6 +43,7 @@ class SelectionRect extends React.Component { const clientRect: {width: number, height: number, left: number, top: number} = parentRef.getBoundingClientRect() const viewBox: {width: number, height: number} = parentRef.closest('svg').viewBox.baseVal // WARNING: elem.closest() is experiemental + console.log(viewBox) const xScale = viewBox.width / clientRect.width const yScale = viewBox.height / clientRect.height diff --git a/protocol-designer/src/components/SingleLabware.js b/protocol-designer/src/components/SingleLabware.js index 7cb5c3fdedc..71be832f703 100644 --- a/protocol-designer/src/components/SingleLabware.js +++ b/protocol-designer/src/components/SingleLabware.js @@ -5,7 +5,7 @@ import {SLOT_WIDTH_MM, SLOT_HEIGHT_MM} from '../constants.js' import styles from './SingleLabware.css' type Props = {className?: string, children?: React.Node} -const LABEL_OFFSET = 8 +const LABEL_OFFSET = 4 /** Simply wraps SVG components like Plate/SelectablePlate with correct dimensions */ export default function SingleLabware (props: Props) { diff --git a/protocol-designer/src/components/labware/NameThisLabwareOverlay.js b/protocol-designer/src/components/labware/NameThisLabwareOverlay.js index cc73ef9de9e..3467827173e 100644 --- a/protocol-designer/src/components/labware/NameThisLabwareOverlay.js +++ b/protocol-designer/src/components/labware/NameThisLabwareOverlay.js @@ -61,16 +61,16 @@ export default class NameThisLabwareOverlay extends React.Component + iconName='water' y='50%' text={i18n.t('deck.overlay.name_labware.add_liquids')} /> + iconName='ot-water-outline' y='75%' text={i18n.t('deck.overlay.name_labware.leave_empty')} /> )} diff --git a/protocol-designer/src/containers/ConnectedDeckSetup.js b/protocol-designer/src/containers/ConnectedDeckSetup.js index dbb0619b046..bd21347e056 100644 --- a/protocol-designer/src/containers/ConnectedDeckSetup.js +++ b/protocol-designer/src/containers/ConnectedDeckSetup.js @@ -4,6 +4,7 @@ import {connect} from 'react-redux' import {Deck, ClickOutside} from '@opentrons/components' import styles from './Deck.css' +import i18n from '../localization' import IngredientSelectionModal from '../components/IngredientSelectionModal.js' import LabwareContainer from '../containers/LabwareContainer.js' @@ -19,7 +20,7 @@ import type {BaseState, ThunkDispatch} from '../types' const ingredSelModIsVisible = activeModals => activeModals.ingredientSelection && activeModals.ingredientSelection.slot type StateProps = { - deckSetupMode: boolean, + selectedTerminalItemId: boolean, ingredSelectionMode: boolean, drilledDown: boolean, _moveLabwareMode: boolean, @@ -29,7 +30,7 @@ type DispatchProps = { drillUpFromLabware: () => mixed, } type Props = { - deckSetupMode: boolean, + selectedTerminalItemId: boolean, header: string, drilledDown: boolean, ingredSelectionMode: boolean, @@ -37,9 +38,7 @@ type Props = { } const mapStateToProps = (state: BaseState): StateProps => ({ - deckSetupMode: ( - steplistSelectors.getSelectedTerminalItemId(state) === START_TERMINAL_ITEM_ID - ), + selectedTerminalItemId: steplistSelectors.getSelectedTerminalItemId(state), // TODO SOON remove all uses of the `activeModals` selector ingredSelectionMode: !!ingredSelModIsVisible(selectors.activeModals(state)), drilledDown: !!selectors.getDrillDownLabwareId(state), @@ -52,7 +51,7 @@ const mapDispatchToProps = (dispatch: ThunkDispatch<*>): DispatchProps => ({ }) const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props => ({ - deckSetupMode: stateProps.deckSetupMode, + selectedTerminalItemId: stateProps.selectedTerminalItemId, ingredSelectionMode: stateProps.ingredSelectionMode, drilledDown: stateProps.drilledDown, handleClickOutside: () => { @@ -63,24 +62,33 @@ const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps): Props // TODO Ian 2018-02-16 this will be broken apart and incorporated into ProtocolEditor class DeckSetup extends React.Component { - renderDeck = () => ( - -
{this.props.header}
-
- {this.props.drilledDown && } - - {({ref}) => ( -
- -
- )} -
-
-
- ) + renderDeck = () => { + const {selectedTerminalItemId} = this.props + return ( + +
+ { + selectedTerminalItemId + ? i18n.t(`deck.header.${selectedTerminalItemId === START_TERMINAL_ITEM_ID ? 'start' : 'end'}`) + : null + } +
+
+ {this.props.drilledDown && } + + {({ref}) => ( +
+ +
+ )} +
+
+
+ ) + } render () { - if (!this.props.deckSetupMode) { + if (this.props.selectedTerminalItemId !== START_TERMINAL_ITEM_ID) { // Temporary quickfix: if we're not in deck setup mode, // hide the labware dropdown and ingredient selection modal // and just show the deck. diff --git a/protocol-designer/src/localization/en/deck.json b/protocol-designer/src/localization/en/deck.json index f2545f8e9bf..1f853e44488 100644 --- a/protocol-designer/src/localization/en/deck.json +++ b/protocol-designer/src/localization/en/deck.json @@ -1,6 +1,13 @@ -"name_labware": { - "nickname_placeholder": "Add a nickname?", - "add_liquids": "Add Liquids", - "leave_empty": "Leave Empty" +{ + "header": { + "start": "Tell the robot where labware and liquids start on the deck", + "end": "Click on labware to inspect the result of your protocol" + }, + "overlay": { + "name_labware": { + "nickname_placeholder": "Add a nickname?", + "add_liquids": "Add Liquids", + "leave_empty": "Leave Empty" + } } } diff --git a/protocol-designer/src/localization/en/index.js b/protocol-designer/src/localization/en/index.js index 40f4e8988b2..7a63e42134c 100644 --- a/protocol-designer/src/localization/en/index.js +++ b/protocol-designer/src/localization/en/index.js @@ -3,7 +3,7 @@ import alert from './alert.json' import button from './button.json' import card from './card.json' -import labware_overlays from './labware_overlays.json' +import deck from './deck.json' import modal from './modal.json' import nav from './nav.json' import step_edit_form from './step_edit_form.json' @@ -15,7 +15,7 @@ export default { alert, button, card, - labware_overlays, + deck, modal, nav, step_edit_form, From 2e526583c089e5c6fcc7651828883abd82f4b2a7 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 16:32:55 -0400 Subject: [PATCH 20/27] deal with label offsets --- .../src/components/IngredientSelectionModal.js | 5 +---- .../src/components/SelectablePlate.js | 17 +++++++++++++---- .../src/components/SelectionRect.js | 9 +++++---- .../src/components/SingleLabware.js | 18 +++++++++++------- .../WellSelectionInput/WellSelectionModal.js | 5 +++-- .../components/labware/BrowseLabwareModal.js | 2 +- .../components/labware/SelectableLabware.js | 14 ++++++++++++-- protocol-designer/src/constants.js | 2 ++ 8 files changed, 48 insertions(+), 24 deletions(-) diff --git a/protocol-designer/src/components/IngredientSelectionModal.js b/protocol-designer/src/components/IngredientSelectionModal.js index b8718d2eb34..7caeac4bfb9 100644 --- a/protocol-designer/src/components/IngredientSelectionModal.js +++ b/protocol-designer/src/components/IngredientSelectionModal.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react' -import SingleLabware from './SingleLabware' import styles from './IngredientSelectionModal.css' import SelectablePlate from '../containers/SelectablePlate' @@ -18,9 +17,7 @@ export default function IngredientSelectionModal (props: Props) { - - - +
diff --git a/protocol-designer/src/components/SelectablePlate.js b/protocol-designer/src/components/SelectablePlate.js index 05041044ba6..2bc5c580ab9 100644 --- a/protocol-designer/src/components/SelectablePlate.js +++ b/protocol-designer/src/components/SelectablePlate.js @@ -17,6 +17,8 @@ import { type Channels, } from '@opentrons/components' +import {WELL_LABEL_OFFSET} from '../constants' +import SingleLabware from '../components/SingleLabware' import SelectionRect from '../components/SelectionRect.js' import type {ContentsByWell} from '../labware-ingred/types' import type {RectEvent} from '../collision-types' @@ -112,11 +114,18 @@ export default function SelectablePlate (props: Props) { } } + // FIXME: SelectionRect is somehow off by one in the x axis, hence the magic number return ( - - - - + + + + + + ) } } diff --git a/protocol-designer/src/components/SelectionRect.js b/protocol-designer/src/components/SelectionRect.js index 7c66946b1a9..df3c2e49793 100644 --- a/protocol-designer/src/components/SelectionRect.js +++ b/protocol-designer/src/components/SelectionRect.js @@ -9,6 +9,8 @@ type Props = { onSelectionDone?: (e: MouseEvent, GenericRect) => mixed, svg?: boolean, // set true if this is an embedded SVG children?: React.Node, + originXOffset?: number, + originYOffset?: number, } type State = { @@ -41,15 +43,14 @@ class SelectionRect extends React.Component { } const clientRect: {width: number, height: number, left: number, top: number} = parentRef.getBoundingClientRect() - const viewBox: {width: number, height: number} = parentRef.closest('svg').viewBox.baseVal // WARNING: elem.closest() is experiemental + let viewBox: {width: number, height: number} = parentRef.closest('svg').viewBox.baseVal // WARNING: elem.closest() is experiemental - console.log(viewBox) const xScale = viewBox.width / clientRect.width const yScale = viewBox.height / clientRect.height return diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 2653f252f11..39d639b2020 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -92,7 +92,7 @@ class WellSelectionModal extends React.Component {
- + id === stepId) + const allWellContentsForStep = allWellContentsForSteps[timelineIdx] const formData = steplistSelectors.getUnsavedForm(state) return { initialSelectedWells: formData ? formData[ownProps.name] : [], pipette: pipetteId ? pipetteSelectors.equippedPipettes(state)[pipetteId] : null, - wellContents: labware ? allWellContentsForSteps[timelineIdx][labware.id] : {}, + wellContents: labware && allWellContentsForStep ? allWellContentsForStep[labware.id] : {}, containerType: labware ? labware.type : 'missing labware', } } diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 3bc81d5846c..38405346941 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -49,7 +49,7 @@ class BrowseLabwareModal extends React.Component { className={modalStyles.modal} contentsClassName={cx(modalStyles.modal_contents, modalStyles.transparent_content)} onCloseClick={this.handleClose}> - + {map(this.props.wellContents, (well, wellName) => ( diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index cf9f5c55f58..1d4038ae9ef 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -13,7 +13,10 @@ import { import {getWellDefsForSVG} from '@opentrons/shared-data' import {getCollidingWells} from '../../utils' -import {SELECTABLE_WELL_CLASS} from '../../constants' +import { + SELECTABLE_WELL_CLASS, + WELL_LABEL_OFFSET, +} from '../../constants' import {getWellSetForMultichannel} from '../../well-selection/utils' import SelectionRect from '../SelectionRect.js' import type {ContentsByWell, Wells} from '../../labware-ingred/types' @@ -101,8 +104,15 @@ class SelectableLabware extends React.Component { return [...acc, ...wellSet] }, []) : Object.keys(selectedWells) + + // FIXME: SelectionRect is somehow off by one in the x axis, hence the magic number return ( - + {map(wellContents, (well, wellName) => ( diff --git a/protocol-designer/src/constants.js b/protocol-designer/src/constants.js index d272e484109..206bd99a3b9 100644 --- a/protocol-designer/src/constants.js +++ b/protocol-designer/src/constants.js @@ -53,3 +53,5 @@ export const DEFAULT_MM_FROM_BOTTOM_ASPIRATE = 1 export const DEFAULT_MM_FROM_BOTTOM_DISPENSE = 0.5 export const DEFAULT_WELL_ORDER_FIRST_OPTION: 't2b' = 't2b' export const DEFAULT_WELL_ORDER_SECOND_OPTION: 'l2r' = 'l2r' + +export const WELL_LABEL_OFFSET: number = 8 From 78b82d211fd31a5a0eb728f2b733efc1c734391d Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 10 Oct 2018 17:09:25 -0400 Subject: [PATCH 21/27] fix up flow errors --- protocol-designer/src/components/SelectionRect.js | 5 +++-- protocol-designer/src/containers/ConnectedDeckSetup.js | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/protocol-designer/src/components/SelectionRect.js b/protocol-designer/src/components/SelectionRect.js index df3c2e49793..0fe4a21bd50 100644 --- a/protocol-designer/src/components/SelectionRect.js +++ b/protocol-designer/src/components/SelectionRect.js @@ -33,6 +33,7 @@ class SelectionRect extends React.Component { const top = Math.min(yStart, yDynamic) const width = Math.abs(xDynamic - xStart) const height = Math.abs(yDynamic - yStart) + const {originXOffset = 0, originYOffset = 0} = this.props if (this.props.svg) { // calculate ratio btw clientRect bounding box vs svg parent viewBox @@ -49,8 +50,8 @@ class SelectionRect extends React.Component { const yScale = viewBox.height / clientRect.height return activeModals.ingredientSelection && activeModals.ingredientSelection.slot type StateProps = { - selectedTerminalItemId: boolean, + selectedTerminalItemId: ?TerminalItemId, ingredSelectionMode: boolean, drilledDown: boolean, _moveLabwareMode: boolean, @@ -30,8 +30,7 @@ type DispatchProps = { drillUpFromLabware: () => mixed, } type Props = { - selectedTerminalItemId: boolean, - header: string, + selectedTerminalItemId: ?TerminalItemId, drilledDown: boolean, ingredSelectionMode: boolean, handleClickOutside: () => void, From bf84053701791d720716a3e0d4f56aabccefd0c5 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 11 Oct 2018 16:17:51 -0400 Subject: [PATCH 22/27] get rid of those zebras --- components/src/deck/LabwareLabels.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/src/deck/LabwareLabels.css b/components/src/deck/LabwareLabels.css index a720766f43a..3517c76ecb7 100644 --- a/components/src/deck/LabwareLabels.css +++ b/components/src/deck/LabwareLabels.css @@ -12,7 +12,3 @@ /* For 384 plate */ font-size: var(--fs-micro); } - -.tiny_labels:nth-child(odd) { - fill: var(--c-light-gray); -} From db2da1c24e04708739027906df2de2378f1c1db7 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 11 Oct 2018 17:22:58 -0400 Subject: [PATCH 23/27] remove sCU from LabwareOnDeck for now... --- .../src/components/labware/LabwareOnDeck.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index e42f1dd8211..381930d736e 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -144,20 +144,22 @@ type LabwareOnDeckProps = { setDefaultLabwareName: () => mixed, } -// TODO: BC 2018-10-08 move these connections to lower level components class LabwareOnDeck extends React.Component { - shouldComponentUpdate (nextProps: LabwareOnDeckProps) { - const shouldAlwaysUpdate = this.props.addLabwareMode || - nextProps.addLabwareMode || - this.props.moveLabwareMode || - nextProps.moveLabwareMode + // TODO: BC 2018-10-11 re-implement this re-render check at a lower level once this component + // and its connected props are broken out into lower level components. - const labwarePresenceChange = this.props.containerId !== nextProps.containerId - const nameOverlayChange = this.props.showNameOverlay !== nextProps.showNameOverlay + // shouldComponentUpdate (nextProps: LabwareOnDeckProps) { + // const shouldAlwaysUpdate = this.props.addLabwareMode || + // nextProps.addLabwareMode || + // this.props.moveLabwareMode || + // nextProps.moveLabwareMode - if (shouldAlwaysUpdate || labwarePresenceChange || nameOverlayChange) return true - return this.props.highlighted !== nextProps.highlighted - } + // const labwarePresenceChange = this.props.containerId !== nextProps.containerId + // const nameOverlayChange = this.props.showNameOverlay !== nextProps.showNameOverlay + + // if (shouldAlwaysUpdate || labwarePresenceChange || nameOverlayChange) return true + // return this.props.highlighted !== nextProps.highlighted + // } render () { const { slot, From ca82bb088ddfaa63512983c451906a5649df0bf4 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Fri, 12 Oct 2018 16:26:23 -0400 Subject: [PATCH 24/27] const not let --- protocol-designer/src/components/SelectionRect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol-designer/src/components/SelectionRect.js b/protocol-designer/src/components/SelectionRect.js index 0fe4a21bd50..f30bc86615f 100644 --- a/protocol-designer/src/components/SelectionRect.js +++ b/protocol-designer/src/components/SelectionRect.js @@ -44,7 +44,7 @@ class SelectionRect extends React.Component { } const clientRect: {width: number, height: number, left: number, top: number} = parentRef.getBoundingClientRect() - let viewBox: {width: number, height: number} = parentRef.closest('svg').viewBox.baseVal // WARNING: elem.closest() is experiemental + const viewBox: {width: number, height: number} = parentRef.closest('svg').viewBox.baseVal // WARNING: elem.closest() is experiemental const xScale = viewBox.width / clientRect.width const yScale = viewBox.height / clientRect.height From 58324f1527b4b7f9c89c96307e40a25f11f1d4c9 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Fri, 12 Oct 2018 16:24:33 -0400 Subject: [PATCH 25/27] working mouse based demo --- components/src/deck/Well.js | 7 ++ components/src/modals/Modal.js | 7 +- .../components/labware/BrowseLabwareModal.js | 85 ++++++++++++++++--- .../src/components/labware/labware.css | 10 +++ .../src/components/steplist/SubstepRow.js | 4 +- .../src/top-selectors/substeps.js | 2 - .../src/top-selectors/well-contents/index.js | 2 + 7 files changed, 99 insertions(+), 18 deletions(-) diff --git a/components/src/deck/Well.js b/components/src/deck/Well.js index b6e92534bf1..7a736ac33ef 100644 --- a/components/src/deck/Well.js +++ b/components/src/deck/Well.js @@ -22,6 +22,8 @@ type Props = { wellDef: WellDefinition, onMouseOver?: (e: SyntheticMouseEvent<*>) => mixed, onMouseLeave?: (e: SyntheticMouseEvent<*>) => mixed, + /** handlers for HoverTooltipComponent */ + hoverTooltipHandlers?: ?HoverTooltipHandlers, } class Well extends React.Component { @@ -40,6 +42,8 @@ class Well extends React.Component { wellDef, onMouseOver, onMouseLeave, + onMouseMove, + hoverTooltipHandlers, } = this.props const fillColor = this.props.fillColor || 'transparent' @@ -59,6 +63,7 @@ class Well extends React.Component { 'data-wellname': wellName, onMouseOver, onMouseLeave, + onMouseMove, } const isRect = wellIsRect(wellDef) @@ -82,6 +87,7 @@ class Well extends React.Component { {/* Border + overlay */} @@ -105,6 +111,7 @@ class Well extends React.Component { {/* Border + overlay */} diff --git a/components/src/modals/Modal.js b/components/src/modals/Modal.js index bd64f8c974b..c45206a0bcc 100644 --- a/components/src/modals/Modal.js +++ b/components/src/modals/Modal.js @@ -17,6 +17,7 @@ type ModalProps = { contentsClassName?: string, /** lightens overlay (alert modal over existing modal)**/ alertOverlay?: boolean, + innerRef?: React.Ref<*>, } /** @@ -24,11 +25,13 @@ type ModalProps = { * with a dark overlay and displays `children` as its contents in a white box */ export default function Modal (props: ModalProps) { - const {contentsClassName, alertOverlay, onCloseClick, heading} = props + const {contentsClassName, alertOverlay, onCloseClick, heading, innerRef} = props return (
-
+
{heading && (

{heading}

)} diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 38405346941..50989332cdb 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -12,14 +12,17 @@ import { LabwareOutline, LabwareLabels, ingredIdsToColor, + HoverTooltip, } from '@opentrons/components' import type {BaseState, ThunkDispatch} from '../../types' import i18n from '../../localization' +import {PillTooltipContents} from '../steplist/SubstepRow' import * as wellContentsSelectors from '../../top-selectors/well-contents' import {selectors} from '../../labware-ingred/reducers' import * as labwareIngredsActions from '../../labware-ingred/actions' import type {ContentsByWell} from '../../labware-ingred/types' +import type {WellIngredientNames} from '../../steplist/types' import SingleLabwareWrapper from '../SingleLabware' @@ -29,6 +32,7 @@ import styles from './labware.css' type SP = { wellContents: ContentsByWell, labwareType: string, + ingredNames: WellIngredientNames, } type DP = { drillUp: () => mixed, @@ -36,7 +40,37 @@ type DP = { type Props = SP & DP +const initialState = { + tooltipX: null, + tooltipY: null, + tooltipWellName: null, + tooltipwellIngreds: null, + tooltipHeight: null, + tooltipWidth: null, +} + +const MOUSE_TOOLTIP_OFFSET_PIXELS = 14 + class BrowseLabwareModal extends React.Component { + state = initialState + wrapperRef + + makeHandleWellMouseOver = (wellName, wellIngreds) => (e) => { + const {clientX, clientY} = e + if (Object.keys(wellIngreds).length > 0 && clientX && clientY && this.wrapperRef) { + this.setState({ + tooltipX: clientX - this.wrapperRef.getBoundingClientRect().left + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipY: clientY - this.wrapperRef.getBoundingClientRect().top + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipWellName: wellName, + tooltipWellIngreds: wellIngreds, + }) + } + } + + handleWellMouseLeave = (e) => { + this.setState(initialState) + } + handleClose = () => { this.props.drillUp() } @@ -46,26 +80,52 @@ class BrowseLabwareModal extends React.Component { return ( { this.wrapperRef = ref }} className={modalStyles.modal} contentsClassName={cx(modalStyles.modal_contents, modalStyles.transparent_content)} onCloseClick={this.handleClose}> - {map(this.props.wellContents, (well, wellName) => ( - - ))} + {map(this.props.wellContents, (well, wellName) => { + const color = ingredIdsToColor(well.groupIds) + const mouseHandlers = color + ? { + onMouseMove: this.makeHandleWellMouseOver(wellName, well.ingreds), + onMouseLeave: this.handleWellMouseLeave, + } + : {} + return ( + + ) + })} + + {this.state.tooltipWellName && +
+
+ +
+
+ }
{i18n.t('modal.browse_labware.instructions')}
) @@ -78,9 +138,10 @@ function mapStateToProps (state: BaseState): SP { const labware = labwareId && allLabware ? allLabware[labwareId] : null const allWellContents = wellContentsSelectors.lastValidWellContents(state) const wellContents = labwareId && allWellContents ? allWellContents[labwareId] : {} - + const ingredNames = selectors.getIngredientNames(state) return { wellContents, + ingredNames, labwareType: labware ? labware.type : 'missing labware', } } diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index 10f426747ed..5947dc546e8 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -81,3 +81,13 @@ font-weight: var(--fw-semibold); color: var(--c-white); } + +.tooltip_box { + @apply --font-body-1-light; + + background-color: var(--c-bg-dark); + box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.13), 0 3px 6px 0 rgba(0, 0, 0, 0.23); + padding: 8px; + cursor: pointer; + z-index: 9001; +} diff --git a/protocol-designer/src/components/steplist/SubstepRow.js b/protocol-designer/src/components/steplist/SubstepRow.js index 46ac1d6f13b..8538036db07 100644 --- a/protocol-designer/src/components/steplist/SubstepRow.js +++ b/protocol-designer/src/components/steplist/SubstepRow.js @@ -26,7 +26,7 @@ type PillTooltipContentsProps = { ingredNames: WellIngredientNames, well: string, } -const PillTooltipContents = (props: PillTooltipContentsProps) => { +export const PillTooltipContents = (props: PillTooltipContentsProps) => { const totalLiquidVolume = reduce(props.ingreds, (acc, ingred) => acc + ingred.volume, 0) const hasMultipleIngreds = Object.keys(props.ingreds).length > 1 return ( @@ -58,7 +58,7 @@ const PillTooltipContents = (props: PillTooltipContentsProps) => {
{`${props.well} Total Volume`} - {totalLiquidVolume}µl + {formatVolume(totalLiquidVolume, 2)}µl
} diff --git a/protocol-designer/src/top-selectors/substeps.js b/protocol-designer/src/top-selectors/substeps.js index ec6584f4461..f51f25eb036 100644 --- a/protocol-designer/src/top-selectors/substeps.js +++ b/protocol-designer/src/top-selectors/substeps.js @@ -20,7 +20,6 @@ export const allSubsteps: Selector = createSelector( steplistSelectors.validatedForms, pipetteSelectors.equippedPipettes, labwareIngredSelectors.getLabwareTypes, - allWellContentsForSteps, steplistSelectors.orderedSteps, fileDataSelectors.robotStateTimeline, fileDataSelectors.getInitialRobotState, @@ -28,7 +27,6 @@ export const allSubsteps: Selector = createSelector( validatedForms, allPipetteData, allLabwareTypes, - _allWellContentsForSteps, orderedSteps, robotStateTimeline, _initialRobotState, diff --git a/protocol-designer/src/top-selectors/well-contents/index.js b/protocol-designer/src/top-selectors/well-contents/index.js index e0f01782154..56b277b8247 100644 --- a/protocol-designer/src/top-selectors/well-contents/index.js +++ b/protocol-designer/src/top-selectors/well-contents/index.js @@ -5,6 +5,7 @@ import mapValues from 'lodash/mapValues' import min from 'lodash/min' import pick from 'lodash/pick' import reduce from 'lodash/reduce' +import omitBy from 'lodash/omitBy' import * as StepGeneration from '../../step-generation' import {selectors as fileDataSelectors} from '../../file-data' @@ -41,6 +42,7 @@ function _wellContentsForWell ( maxVolume: Infinity, // TODO Ian 2018-03-23 refactor so all these fields aren't needed wellName: well, groupIds: ingredGroupIdsWithContent, // TODO: BC 2018-09-21 remove in favor of volumeByGroupId + ingreds: omitBy(liquidVolState, (ingredData) => !ingredData || ingredData.volume <= 0), } } From ff3ad2ebf3694bfb92c710b3e7f877a705b00905 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Fri, 12 Oct 2018 17:09:35 -0400 Subject: [PATCH 26/27] feat(protocol-designer): add tooltips on hover of final result wells Add a mouse anchored tooltips on hover of wells with ingredients in the final results labware drilldown including the same information as the ingredient pill tooltips in the side bar. Closes #2409 --- components/src/deck/Well.js | 7 +-- .../components/labware/BrowseLabwareModal.js | 47 +++++++++++-------- .../src/components/steplist/SubstepRow.js | 3 +- protocol-designer/src/labware-ingred/types.js | 3 +- .../src/top-selectors/substeps.js | 1 - 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/components/src/deck/Well.js b/components/src/deck/Well.js index 7a736ac33ef..33fac6bbb39 100644 --- a/components/src/deck/Well.js +++ b/components/src/deck/Well.js @@ -5,7 +5,6 @@ import cx from 'classnames' import {wellIsRect, type WellDefinition} from '@opentrons/shared-data' import styles from './Well.css' import {SELECTABLE_WELL_CLASS} from '../constants.js' -// import WellToolTip from '../components/WellToolTip.js' // TODO bring back tooltip in SVG, somehow export type SingleWell = {| wellName: string, @@ -22,8 +21,7 @@ type Props = { wellDef: WellDefinition, onMouseOver?: (e: SyntheticMouseEvent<*>) => mixed, onMouseLeave?: (e: SyntheticMouseEvent<*>) => mixed, - /** handlers for HoverTooltipComponent */ - hoverTooltipHandlers?: ?HoverTooltipHandlers, + onMouseMove?: (e: SyntheticMouseEvent<*>) => mixed, } class Well extends React.Component { @@ -43,7 +41,6 @@ class Well extends React.Component { onMouseOver, onMouseLeave, onMouseMove, - hoverTooltipHandlers, } = this.props const fillColor = this.props.fillColor || 'transparent' @@ -87,7 +84,6 @@ class Well extends React.Component { {/* Border + overlay */} @@ -111,7 +107,6 @@ class Well extends React.Component { {/* Border + overlay */} diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 50989332cdb..bf8a13e3eaa 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -12,11 +12,11 @@ import { LabwareOutline, LabwareLabels, ingredIdsToColor, - HoverTooltip, } from '@opentrons/components' import type {BaseState, ThunkDispatch} from '../../types' import i18n from '../../localization' +import type {LocationLiquidState} from '../../step-generation' import {PillTooltipContents} from '../steplist/SubstepRow' import * as wellContentsSelectors from '../../top-selectors/well-contents' import {selectors} from '../../labware-ingred/reducers' @@ -40,32 +40,39 @@ type DP = { type Props = SP & DP -const initialState = { +type State = { + tooltipX: ?number, + tooltipY: ?number, + tooltipWellName: ?string, + tooltipWellIngreds: ?LocationLiquidState, +} +const initialState: State = { tooltipX: null, tooltipY: null, tooltipWellName: null, - tooltipwellIngreds: null, - tooltipHeight: null, - tooltipWidth: null, + tooltipWellIngreds: null, } const MOUSE_TOOLTIP_OFFSET_PIXELS = 14 -class BrowseLabwareModal extends React.Component { - state = initialState - wrapperRef - - makeHandleWellMouseOver = (wellName, wellIngreds) => (e) => { - const {clientX, clientY} = e - if (Object.keys(wellIngreds).length > 0 && clientX && clientY && this.wrapperRef) { - this.setState({ - tooltipX: clientX - this.wrapperRef.getBoundingClientRect().left + MOUSE_TOOLTIP_OFFSET_PIXELS, - tooltipY: clientY - this.wrapperRef.getBoundingClientRect().top + MOUSE_TOOLTIP_OFFSET_PIXELS, - tooltipWellName: wellName, - tooltipWellIngreds: wellIngreds, - }) +class BrowseLabwareModal extends React.Component { + state: State = initialState + wrapperRef: ?any + + makeHandleWellMouseOver = (wellName: string, wellIngreds: LocationLiquidState) => + (e) => { + const {clientX, clientY} = e + if (Object.keys(wellIngreds).length > 0 && clientX && clientY && this.wrapperRef) { + const wrapperLeft = this.wrapperRef ? this.wrapperRef.getBoundingClientRect().left : 0 + const wrapperTop = this.wrapperRef ? this.wrapperRef.getBoundingClientRect().top : 0 + this.setState({ + tooltipX: clientX - wrapperLeft + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipY: clientY - wrapperTop + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipWellName: wellName, + tooltipWellIngreds: wellIngreds, + }) + } } - } handleWellMouseLeave = (e) => { this.setState(initialState) @@ -122,7 +129,7 @@ class BrowseLabwareModal extends React.Component { + ingreds={this.state.tooltipWellIngreds || {}} />
} diff --git a/protocol-designer/src/components/steplist/SubstepRow.js b/protocol-designer/src/components/steplist/SubstepRow.js index 8538036db07..adbee30ca28 100644 --- a/protocol-designer/src/components/steplist/SubstepRow.js +++ b/protocol-designer/src/components/steplist/SubstepRow.js @@ -5,6 +5,7 @@ import reduce from 'lodash/reduce' import omitBy from 'lodash/omitBy' import {HoverTooltip, swatchColors} from '@opentrons/components' +import type {LocationLiquidState} from '../../step-generation' import type {SubstepWellData, WellIngredientVolumeData, WellIngredientNames} from '../../steplist/types' import IngredPill from './IngredPill' import {PDListItem} from '../lists' @@ -22,7 +23,7 @@ type SubstepRowProps = {| |} type PillTooltipContentsProps = { - ingreds: WellIngredientVolumeData, + ingreds: WellIngredientVolumeData | LocationLiquidState, ingredNames: WellIngredientNames, well: string, } diff --git a/protocol-designer/src/labware-ingred/types.js b/protocol-designer/src/labware-ingred/types.js index 4de8f67b6ef..17dd0da7a1a 100644 --- a/protocol-designer/src/labware-ingred/types.js +++ b/protocol-designer/src/labware-ingred/types.js @@ -1,5 +1,5 @@ // @flow -import type {LabwareData} from '../step-generation' +import type {LabwareData, LocationLiquidState} from '../step-generation' // TODO Ian 2018-02-19 make these shared in component library, standardize with Run App // ===== LABWARE =========== @@ -27,6 +27,7 @@ export type WellContents = {| // non-ingredient well state, for SelectablePlate maxVolume: number, wellName: string, // eg 'A1', 'A2' etc groupIds: Array, + ingreds: LocationLiquidState, |} export type ContentsByWell = { diff --git a/protocol-designer/src/top-selectors/substeps.js b/protocol-designer/src/top-selectors/substeps.js index f51f25eb036..ef5013a4efd 100644 --- a/protocol-designer/src/top-selectors/substeps.js +++ b/protocol-designer/src/top-selectors/substeps.js @@ -5,7 +5,6 @@ import {selectors as pipetteSelectors} from '../pipettes' import {selectors as labwareIngredSelectors} from '../labware-ingred/reducers' import {selectors as steplistSelectors} from '../steplist' import {selectors as fileDataSelectors} from '../file-data' -import {allWellContentsForSteps} from './well-contents' import { generateSubsteps, From f463a2164683eeb379e028da11fefacee47a84d8 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 15 Oct 2018 16:08:39 -0400 Subject: [PATCH 27/27] add deck title bar and hover well state --- .../src/components/labware/BrowseLabwareModal.js | 1 + .../src/containers/ConnectedTitleBar.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 681639fb28a..153c725ef90 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -107,6 +107,7 @@ class BrowseLabwareModal extends React.Component { {...mouseHandlers} key={wellName} wellName={wellName} + highlighted={this.state.tooltipWellName === wellName} fillColor={color} svgOffset={{x: 1, y: -3}} wellDef={allWellDefsByName[wellName]} /> diff --git a/protocol-designer/src/containers/ConnectedTitleBar.js b/protocol-designer/src/containers/ConnectedTitleBar.js index e46da211454..0f27b51f714 100644 --- a/protocol-designer/src/containers/ConnectedTitleBar.js +++ b/protocol-designer/src/containers/ConnectedTitleBar.js @@ -49,6 +49,7 @@ function mapStateToProps (state: BaseState): SP { const labware = labwareIngredSelectors.getSelectedContainer(state) const labwareNames = labwareIngredSelectors.getLabwareNames(state) const labwareNickname = labware && labware.id && labwareNames[labware.id] + const drilledDownLabwareId = labwareIngredSelectors.getDrillDownLabwareId(state) switch (_page) { case 'liquids': @@ -83,14 +84,22 @@ function mapStateToProps (state: BaseState): SP { // NOTE: this default case error should never be reached, it's just a sanity check if (_page !== 'steplist') console.error('ConnectedTitleBar got an unsupported page, returning steplist instead') let subtitle + let backButtonLabel + let title if (selectedTerminalId === START_TERMINAL_ITEM_ID) { subtitle = START_TERMINAL_TITLE } else if (selectedTerminalId === END_TERMINAL_ITEM_ID) { subtitle = END_TERMINAL_TITLE + if (drilledDownLabwareId) { + backButtonLabel = 'Deck' + const drilledDownLabware = labwareIngredSelectors.getLabware(state)[drilledDownLabwareId] + title = drilledDownLabware && drilledDownLabware.name + subtitle = drilledDownLabware && humanizeLabwareType(drilledDownLabware.type) + } } else if (selectedStep) { subtitle = selectedStep.title } - return { _page: 'steplist', title: fileName, subtitle } + return { _page: 'steplist', title: title || fileName, subtitle, backButtonLabel } } } } @@ -109,6 +118,10 @@ function mergeProps (stateProps: SP, dispatchProps: {dispatch: Dispatch<*>}): Pr onBackClick = () => dispatch(closeWellSelectionModal()) } + if (_page === 'steplist' && props.backButtonLabel) { + onBackClick = () => {} + } + return { ...props, onBackClick,