Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(protocol-designer): add tooltips on hover of final result wells #2479

Merged
merged 32 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
21e7658
split out selectable labware component begin
b-cooper Oct 3, 2018
871b1cd
Merge branch 'edge' into pd_selectable-labware
b-cooper Oct 4, 2018
c0b60b7
selecting wells
b-cooper Oct 4, 2018
5d58fc7
Merge branch 'edge' into pd_selectable-labware
b-cooper Oct 4, 2018
d4f2ab9
clean up the edges of the new well selection modal and input
b-cooper Oct 4, 2018
3c4bad3
should component update in the labware or well, and disconnect ingred…
b-cooper Oct 4, 2018
7c78058
labware on deck no hover or select
b-cooper Oct 8, 2018
9322abc
feat(protocol-designer): separate and execute performance audit of la…
b-cooper Oct 8, 2018
f5c565e
fix move labware bug
b-cooper Oct 8, 2018
f8685a0
merge edge and iron out conflicts
b-cooper Oct 9, 2018
2c4214b
sneaky flow errors
b-cooper Oct 9, 2018
a135080
wells not selectable
b-cooper Oct 9, 2018
bd69f61
fix bugs that ian uncovered
b-cooper Oct 9, 2018
967492d
add comment
b-cooper Oct 10, 2018
90d3339
fix up extra setMoveLabwareMode
b-cooper Oct 8, 2018
8c0f9e6
building out
b-cooper Oct 8, 2018
a0188fd
debug browse labware modal on render
b-cooper Oct 8, 2018
fbd80bc
feat(protocol-designer): create view to browse final liquid state
b-cooper Oct 9, 2018
33317bb
update labels always
b-cooper Oct 10, 2018
8788296
protect browseable modal
b-cooper Oct 10, 2018
ce2fff7
save game
b-cooper Oct 10, 2018
e1e322e
trouble shooting offset rect
b-cooper Oct 10, 2018
2e52658
deal with label offsets
b-cooper Oct 10, 2018
78b82d2
fix up flow errors
b-cooper Oct 10, 2018
4a5d7c2
merge updates from edge
b-cooper Oct 11, 2018
bf84053
get rid of those zebras
b-cooper Oct 11, 2018
db2da1c
remove sCU from LabwareOnDeck for now...
b-cooper Oct 11, 2018
ca82bb0
const not let
b-cooper Oct 12, 2018
58324f1
working mouse based demo
b-cooper Oct 12, 2018
ff3ad2e
feat(protocol-designer): add tooltips on hover of final result wells
b-cooper Oct 12, 2018
7fdc98a
merge edge
b-cooper Oct 12, 2018
f463a21
add deck title bar and hover well state
b-cooper Oct 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion components/src/deck/Well.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,6 +21,7 @@ type Props = {
wellDef: WellDefinition,
onMouseOver?: (e: SyntheticMouseEvent<*>) => mixed,
onMouseLeave?: (e: SyntheticMouseEvent<*>) => mixed,
onMouseMove?: (e: SyntheticMouseEvent<*>) => mixed,
}

class Well extends React.Component<Props> {
Expand All @@ -40,6 +40,7 @@ class Well extends React.Component<Props> {
wellDef,
onMouseOver,
onMouseLeave,
onMouseMove,
} = this.props

const fillColor = this.props.fillColor || 'transparent'
Expand All @@ -59,6 +60,7 @@ class Well extends React.Component<Props> {
'data-wellname': wellName,
onMouseOver,
onMouseLeave,
onMouseMove,
}

const isRect = wellIsRect(wellDef)
Expand Down
7 changes: 5 additions & 2 deletions components/src/modals/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@ type ModalProps = {
contentsClassName?: string,
/** lightens overlay (alert modal over existing modal)**/
alertOverlay?: boolean,
innerRef?: React.Ref<*>,
}

/**
* Base modal component that fills its nearest `display:relative` ancestor
* 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 (
<div className={cx(styles.modal, props.className, {[styles.alert_modal]: alertOverlay})} >
<Overlay onClick={onCloseClick} alertOverlay={alertOverlay}/>
<div className={cx(styles.modal_contents, contentsClassName)}>
<div
ref={innerRef}
className={cx(styles.modal_contents, contentsClassName)}>
{heading && (
<h3 className={styles.modal_heading}>{heading}</h3>
)}
Expand Down
92 changes: 79 additions & 13 deletions protocol-designer/src/components/labware/BrowseLabwareModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import {
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'
import * as labwareIngredsActions from '../../labware-ingred/actions'
import type {ContentsByWell} from '../../labware-ingred/types'
import type {WellIngredientNames} from '../../steplist/types'

import SingleLabwareWrapper from '../SingleLabware'

Expand All @@ -29,14 +32,52 @@ import styles from './labware.css'
type SP = {
wellContents: ContentsByWell,
labwareType: string,
ingredNames: WellIngredientNames,
}
type DP = {
drillUp: () => mixed,
}

type Props = SP & DP

class BrowseLabwareModal extends React.Component<Props> {
type State = {
tooltipX: ?number,
tooltipY: ?number,
tooltipWellName: ?string,
tooltipWellIngreds: ?LocationLiquidState,
}
const initialState: State = {
tooltipX: null,
tooltipY: null,
tooltipWellName: null,
tooltipWellIngreds: null,
}

const MOUSE_TOOLTIP_OFFSET_PIXELS = 14

class BrowseLabwareModal extends React.Component<Props, State> {
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)
}

handleClose = () => {
this.props.drillUp()
}
Expand All @@ -46,26 +87,50 @@ class BrowseLabwareModal extends React.Component<Props> {

return (
<Modal
innerRef={ref => { this.wrapperRef = ref }}
className={modalStyles.modal}
contentsClassName={cx(modalStyles.modal_contents, modalStyles.transparent_content)}
onCloseClick={this.handleClose}>
<SingleLabwareWrapper showLabels>
<g>
<LabwareOutline />
{map(this.props.wellContents, (well, wellName) => (
<Well
selectable
key={wellName}
wellName={wellName}
highlighted={well.highlighted}
selected={well.selected}
fillColor={ingredIdsToColor(well.groupIds)}
svgOffset={{x: 1, y: -3}}
wellDef={allWellDefsByName[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 (
<Well
{...mouseHandlers}
key={wellName}
wellName={wellName}
highlighted={this.state.tooltipWellName === wellName}
fillColor={color}
svgOffset={{x: 1, y: -3}}
wellDef={allWellDefsByName[wellName]} />
)
})}
</g>
<LabwareLabels labwareType={this.props.labwareType} inner={false} />
</SingleLabwareWrapper>
{this.state.tooltipWellName &&
<div
style={{
left: this.state.tooltipX,
top: this.state.tooltipY,
position: 'absolute',
}}>
<div className={styles.tooltip_box}>
<PillTooltipContents
well={this.state.tooltipWellName}
ingredNames={this.props.ingredNames}
ingreds={this.state.tooltipWellIngreds || {}} />
</div>
</div>
}
<div className={styles.modal_instructions}>{i18n.t('modal.browse_labware.instructions')}</div>
</Modal>
)
Expand All @@ -78,9 +143,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',
}
}
Expand Down
10 changes: 10 additions & 0 deletions protocol-designer/src/components/labware/labware.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 4 additions & 3 deletions protocol-designer/src/components/steplist/SubstepRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -22,11 +23,11 @@ type SubstepRowProps = {|
|}

type PillTooltipContentsProps = {
ingreds: WellIngredientVolumeData,
ingreds: WellIngredientVolumeData | LocationLiquidState,
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 (
Expand Down Expand Up @@ -58,7 +59,7 @@ const PillTooltipContents = (props: PillTooltipContentsProps) => {
<div className={styles.total_divider}></div>
<div className={styles.total_row}>
<span>{`${props.well} Total Volume`}</span>
<span>{totalLiquidVolume}µl</span>
<span>{formatVolume(totalLiquidVolume, 2)}µl</span>
</div>
</React.Fragment>
}
Expand Down
15 changes: 14 additions & 1 deletion protocol-designer/src/containers/ConnectedTitleBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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 }
}
}
}
Expand All @@ -109,6 +118,10 @@ function mergeProps (stateProps: SP, dispatchProps: {dispatch: Dispatch<*>}): Pr
onBackClick = () => dispatch(closeWellSelectionModal())
}

if (_page === 'steplist' && props.backButtonLabel) {
onBackClick = () => {}
}

return {
...props,
onBackClick,
Expand Down
3 changes: 2 additions & 1 deletion protocol-designer/src/labware-ingred/types.js
Original file line number Diff line number Diff line change
@@ -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 ===========
Expand Down Expand Up @@ -27,6 +27,7 @@ export type WellContents = {| // non-ingredient well state, for SelectablePlate
maxVolume: number,
wellName: string, // eg 'A1', 'A2' etc
groupIds: Array<string>,
ingreds: LocationLiquidState,
|}

export type ContentsByWell = {
Expand Down
3 changes: 0 additions & 3 deletions protocol-designer/src/top-selectors/substeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,15 +19,13 @@ export const allSubsteps: Selector<AllSubsteps> = createSelector(
steplistSelectors.validatedForms,
pipetteSelectors.equippedPipettes,
labwareIngredSelectors.getLabwareTypes,
allWellContentsForSteps,
steplistSelectors.orderedSteps,
fileDataSelectors.robotStateTimeline,
fileDataSelectors.getInitialRobotState,
(
validatedForms,
allPipetteData,
allLabwareTypes,
_allWellContentsForSteps,
orderedSteps,
robotStateTimeline,
_initialRobotState,
Expand Down
2 changes: 2 additions & 0 deletions protocol-designer/src/top-selectors/well-contents/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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),
}
}

Expand Down