From 4e943321ad347bf3647acc129c631fcaf82c1bd1 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 16 Oct 2018 16:51:04 -0400 Subject: [PATCH 01/10] begin to abstract out wellTooltip --- protocol-designer/src/components/labware/WellTooltip.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 protocol-designer/src/components/labware/WellTooltip.js diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js new file mode 100644 index 00000000000..e69de29bb2d From 51dabdc224c67c9e75925dddca857164e0ecbd1f Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 16 Oct 2018 16:51:14 -0400 Subject: [PATCH 02/10] begin to abstract out wellTooltip --- .../src/components/labware/WellTooltip.js | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js index e69de29bb2d..88153976303 100644 --- a/protocol-designer/src/components/labware/WellTooltip.js +++ b/protocol-designer/src/components/labware/WellTooltip.js @@ -0,0 +1,129 @@ +// @flow +import * as React from 'react' +import cx from 'classnames' +import map from 'lodash/map' +import {connect} from 'react-redux' + +import {getWellDefsForSVG} from '@opentrons/shared-data' + +import { + Modal, + Well, + LabwareOutline, + LabwareLabels, + ingredIdsToColor, +} 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' +import * as labwareIngredsActions from '../../labware-ingred/actions' +import type {ContentsByWell} from '../../labware-ingred/types' +import type {WellIngredientNames} from '../../steplist/types' + +import SingleLabwareWrapper from '../SingleLabware' + +import modalStyles from '../modals/modal.css' +import styles from './labware.css' + +type SP = { + wellContents: ContentsByWell, + labwareType: string, + ingredNames: WellIngredientNames, +} +type DP = { + drillUp: () => mixed, +} + +type Props = SP & DP + +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 { + 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() + } + + render () { + const allWellDefsByName = getWellDefsForSVG(this.props.labwareType) + + return ( + + {()this.props.children} + {this.state.tooltipWellName && +
+
+ +
+
+ } +
+ ) + } +} + +function mapStateToProps (state: BaseState): SP { + const labwareId = selectors.getDrillDownLabwareId(state) + const allLabware = selectors.getLabware(state) + 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', + } +} + +function mapDispatchToProps (dispatch: ThunkDispatch<*>): DP { + return {drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware())} +} + +export default connect(mapStateToProps, mapDispatchToProps)(BrowseLabwareModal) From 7cc80913510af5d4e5680a747783fcb09513889a Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 17 Oct 2018 17:24:12 -0400 Subject: [PATCH 03/10] make some changes --- components/src/utils.js | 1 + .../components/labware/BrowseLabwareModal.js | 111 ++++++------------ .../src/components/labware/WellTooltip.js | 96 ++++----------- 3 files changed, 56 insertions(+), 152 deletions(-) diff --git a/components/src/utils.js b/components/src/utils.js index 14e51ad68be..d24a88001b0 100644 --- a/components/src/utils.js +++ b/components/src/utils.js @@ -3,6 +3,7 @@ import startCase from 'lodash/startCase' import { swatchColors, MIXED_WELL_COLOR, + AIR, } from '@opentrons/components' import {AIR} from './constants' diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 153c725ef90..02b822a0516 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -16,8 +16,6 @@ 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' @@ -28,6 +26,7 @@ import SingleLabwareWrapper from '../SingleLabware' import modalStyles from '../modals/modal.css' import styles from './labware.css' +import WellTooltip from './WellTooltip' type SP = { wellContents: ContentsByWell, @@ -40,44 +39,9 @@ type DP = { type Props = SP & DP -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 { - state: State = initialState +class BrowseLabwareModal extends React.Component { 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() } @@ -91,46 +55,37 @@ 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) => { - const color = ingredIdsToColor(well.groupIds) - const mouseHandlers = color - ? { - onMouseMove: this.makeHandleWellMouseOver(wellName, well.ingreds), - onMouseLeave: this.handleWellMouseLeave, - } - : {} - return ( - - ) - })} - - - - {this.state.tooltipWellName && -
-
- -
-
- } + + { + ({makeHandleMouseMove, handleMouseLeaveWell, tooltipWellName}) => ( + + + + {map(this.props.wellContents, (well, wellName) => { + const color = ingredIdsToColor(well.groupIds) + const mouseHandlers = color + ? { + onMouseMove: makeHandleMouseMove(wellName, well.ingreds), + onMouseLeave: handleMouseLeaveWell, + } + : {} + return ( + + ) + })} + + + + ) + } +
{i18n.t('modal.browse_labware.instructions')}
) diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js index 88153976303..78c322658d1 100644 --- a/protocol-designer/src/components/labware/WellTooltip.js +++ b/protocol-designer/src/components/labware/WellTooltip.js @@ -1,44 +1,13 @@ // @flow import * as React from 'react' -import cx from 'classnames' -import map from 'lodash/map' -import {connect} from 'react-redux' - -import {getWellDefsForSVG} from '@opentrons/shared-data' - -import { - Modal, - Well, - LabwareOutline, - LabwareLabels, - ingredIdsToColor, -} 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' -import * as labwareIngredsActions from '../../labware-ingred/actions' -import type {ContentsByWell} from '../../labware-ingred/types' import type {WellIngredientNames} from '../../steplist/types' -import SingleLabwareWrapper from '../SingleLabware' - -import modalStyles from '../modals/modal.css' import styles from './labware.css' -type SP = { - wellContents: ContentsByWell, - labwareType: string, - ingredNames: WellIngredientNames, -} -type DP = { - drillUp: () => mixed, -} - -type Props = SP & DP +type Props = {ingredNames: WellIngredientNames, wrapperRef: React.Element<*>} type State = { tooltipX: ?number, @@ -55,39 +24,36 @@ const initialState: State = { const MOUSE_TOOLTIP_OFFSET_PIXELS = 14 -class BrowseLabwareModal extends React.Component { +class WellTooltip 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, - }) - } + makeHandleMouseMove = (wellName: string, wellIngreds: LocationLiquidState) => (e) => { + const {pageX, pageY} = e + if (Object.keys(wellIngreds).length > 0 && pageX && pageY) { + const wrapperLeft = 0//this.props.wrapperRef ? this.props.wrapperRef.getBoundingpageRect().left : 0 + const wrapperTop = 0//this.props.wrapperRef ? this.props.wrapperRef.getBoundingpageRect().top : 0 + console.table({e, wellName, wellIngreds, pageX, pageY}) + this.setState({ + tooltipX: pageX - wrapperLeft + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipY: pageY - wrapperTop + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipWellName: wellName, + tooltipWellIngreds: wellIngreds, + }) } - - handleWellMouseLeave = (e) => { - this.setState(initialState) } - handleClose = () => { - this.props.drillUp() + handleMouseLeaveWell = (e) => { + this.setState(initialState) } render () { - const allWellDefsByName = getWellDefsForSVG(this.props.labwareType) - return ( - {()this.props.children} + {this.props.children({ + makeHandleMouseMove: this.makeHandleMouseMove, + handleMouseLeaveWell: this.handleMouseLeaveWell, + tooltipWellName: this.state.tooltipWellName, + })} {this.state.tooltipWellName &&
{ } } -function mapStateToProps (state: BaseState): SP { - const labwareId = selectors.getDrillDownLabwareId(state) - const allLabware = selectors.getLabware(state) - 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', - } -} - -function mapDispatchToProps (dispatch: ThunkDispatch<*>): DP { - return {drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware())} -} - -export default connect(mapStateToProps, mapDispatchToProps)(BrowseLabwareModal) +export default WellTooltip From 65b295609dab0ef84352f0753403302419d53ee4 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 18 Oct 2018 16:03:16 -0400 Subject: [PATCH 04/10] save game --- components/src/utils.js | 2 - protocol-designer/src/components/App.js | 2 + .../src/components/labware/WellTooltip.js | 84 +++++++++++++------ .../src/components/labware/labware.css | 3 +- .../src/components/portals/TopPortal.js | 31 +++++++ 5 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 protocol-designer/src/components/portals/TopPortal.js diff --git a/components/src/utils.js b/components/src/utils.js index d24a88001b0..ec2f517b21c 100644 --- a/components/src/utils.js +++ b/components/src/utils.js @@ -6,8 +6,6 @@ import { AIR, } from '@opentrons/components' -import {AIR} from './constants' - export const humanizeLabwareType = startCase export const wellNameSplit = (wellName: string): [string, string] => { diff --git a/protocol-designer/src/components/App.js b/protocol-designer/src/components/App.js index 141af7babcc..9dea1e08de1 100644 --- a/protocol-designer/src/components/App.js +++ b/protocol-designer/src/components/App.js @@ -5,6 +5,7 @@ import { HashRouter, Route } from 'react-router-dom' import ConnectedDeckSetup from '../containers/ConnectedDeckSetup' import About from './About' import ProtocolEditor from './ProtocolEditor' +import {PortalRoot} from './portals/TopPortal' import '../css/reset.css' @@ -16,6 +17,7 @@ export default function App () { {/* TODO: Ian 2018-06-08 remove these unused routes & their components */} +
) diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js index 78c322658d1..f8cc4def85c 100644 --- a/protocol-designer/src/components/labware/WellTooltip.js +++ b/protocol-designer/src/components/labware/WellTooltip.js @@ -1,13 +1,15 @@ // @flow import * as React from 'react' +import {Popper, Reference, Manager} from 'react-popper' import type {LocationLiquidState} from '../../step-generation' import {PillTooltipContents} from '../steplist/SubstepRow' import type {WellIngredientNames} from '../../steplist/types' +import {Portal} from '../portals/TopPortal' import styles from './labware.css' -type Props = {ingredNames: WellIngredientNames, wrapperRef: React.Element<*>} +type Props = {ingredNames: WellIngredientNames} type State = { tooltipX: ?number, @@ -24,18 +26,29 @@ const initialState: State = { const MOUSE_TOOLTIP_OFFSET_PIXELS = 14 +type MouseTargetProps = {top: number, left: number} +class VirtualMouseTarget extends React.Component { + render () { + return ( +
+ ) + } +} class WellTooltip extends React.Component { state: State = initialState + mouseRef makeHandleMouseMove = (wellName: string, wellIngreds: LocationLiquidState) => (e) => { const {pageX, pageY} = e if (Object.keys(wellIngreds).length > 0 && pageX && pageY) { - const wrapperLeft = 0//this.props.wrapperRef ? this.props.wrapperRef.getBoundingpageRect().left : 0 - const wrapperTop = 0//this.props.wrapperRef ? this.props.wrapperRef.getBoundingpageRect().top : 0 - console.table({e, wellName, wellIngreds, pageX, pageY}) this.setState({ - tooltipX: pageX - wrapperLeft + MOUSE_TOOLTIP_OFFSET_PIXELS, - tooltipY: pageY - wrapperTop + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipX: pageX + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipY: pageY + MOUSE_TOOLTIP_OFFSET_PIXELS, tooltipWellName: wellName, tooltipWellIngreds: wellIngreds, }) @@ -47,28 +60,47 @@ class WellTooltip extends React.Component { } render () { + const {tooltipX, tooltipY} = this.state + return ( - {this.props.children({ - makeHandleMouseMove: this.makeHandleMouseMove, - handleMouseLeaveWell: this.handleMouseLeaveWell, - tooltipWellName: this.state.tooltipWellName, - })} - {this.state.tooltipWellName && -
-
- -
-
- } + + + {({ref}) => ( + +
+
+ ) + } +
+ {this.props.children({ + makeHandleMouseMove: this.makeHandleMouseMove, + handleMouseLeaveWell: this.handleMouseLeaveWell, + tooltipWellName: this.state.tooltipWellName, + })} + {this.state.tooltipWellName && + + {({ref, style, placement}) => { + console.log('tried Render', style, placement) + return ( + +
+ +
+
+ ) + }} +
+ } +
) } diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index 5947dc546e8..b68a33741ec 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -89,5 +89,6 @@ 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; + z-index: 10001; + position: absolute; } diff --git a/protocol-designer/src/components/portals/TopPortal.js b/protocol-designer/src/components/portals/TopPortal.js new file mode 100644 index 00000000000..f4e81df06c1 --- /dev/null +++ b/protocol-designer/src/components/portals/TopPortal.js @@ -0,0 +1,31 @@ +// @flow +import * as React from 'react' +import ReactDom from 'react-dom' + +const PORTAL_ROOT_ID = 'top-portal-root' + +export function PortalRoot () { + return
+} + +export function getPortalElem () { + return document.getElementById(PORTAL_ROOT_ID) +} + +type Props = {children: React.Node} + +/** The children of Portal are rendered into the + * PortalRoot, if the PortalRoot exists in the DOM */ +export function Portal (props: Props): React.Node { + const modalRootElem = getPortalElem() + + if (!modalRootElem) { + console.error('Confirm Modal root is not present, could not render modal') + return null + } + + return ReactDom.createPortal( + props.children, + modalRootElem + ) +} From 66352c47dc71f8e843f54685c4c38b696e980e53 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Fri, 19 Oct 2018 16:42:10 -0400 Subject: [PATCH 05/10] over well not move --- .../WellSelectionInput/WellSelectionModal.js | 9 ++- .../src/components/WellToolTip.css | 30 --------- .../src/components/WellToolTip.js | 40 ------------ .../components/labware/BrowseLabwareModal.js | 7 +- .../components/labware/SelectableLabware.js | 52 +++++++++------ .../src/components/labware/WellTooltip.js | 50 ++++++-------- .../src/components/labware/labware.css | 65 +++++++++++++++++++ 7 files changed, 128 insertions(+), 125 deletions(-) delete mode 100644 protocol-designer/src/components/WellToolTip.css delete mode 100644 protocol-designer/src/components/WellToolTip.js diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 5fe4fa9bbd0..694a75ca730 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -13,11 +13,12 @@ import {selectors as pipetteSelectors} from '../../../pipettes' import * as wellContentsSelectors from '../../../top-selectors/well-contents' import {selectors} from '../../../labware-ingred/reducers' +import type {Wells, ContentsByWell} from '../../../labware-ingred/types' import {selectors as steplistSelectors} from '../../../steplist' +import type {WellIngredientNames} from '../../steplist/types' 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' @@ -38,6 +39,7 @@ type SP = { initialSelectedWells: Array, wellContents: ContentsByWell, containerType: string, + ingredNames: WellIngredientNames, } type DP = {saveWellSelection: (Wells) => mixed} @@ -101,7 +103,8 @@ class WellSelectionModal extends React.Component { updateHighlightedWells={this.updateHighlightedWells} wellContents={this.props.wellContents} containerType={this.props.containerType} - pipetteChannels={pipette && pipette.channels} /> + pipetteChannels={pipette && pipette.channels} + ingredNames={this.props.ingredNames} /> @@ -123,12 +126,14 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { const timelineIdx = orderedSteps.findIndex(id => id === stepId) const allWellContentsForStep = allWellContentsForSteps[timelineIdx] const formData = steplistSelectors.getUnsavedForm(state) + const ingredNames = selectors.getIngredientNames(state) return { initialSelectedWells: formData ? formData[ownProps.name] : [], pipette: pipetteId ? pipetteSelectors.equippedPipettes(state)[pipetteId] : null, wellContents: labware && allWellContentsForStep ? allWellContentsForStep[labware.id] : {}, containerType: labware ? labware.type : 'missing labware', + ingredNames, } } diff --git a/protocol-designer/src/components/WellToolTip.css b/protocol-designer/src/components/WellToolTip.css deleted file mode 100644 index dd2c2dd5835..00000000000 --- a/protocol-designer/src/components/WellToolTip.css +++ /dev/null @@ -1,30 +0,0 @@ -.well_tool_tip { - position: absolute; - font-size: 1rem; - text-align: left; - background-color: white; - border: 1px solid gray; - padding: 0.5em; - z-index: 1; - top: 50px; -} - -.well_tool_tip h1 { - font-size: 1.5em; - margin: 0; -} - -.info_row { - display: flex; -} - -.info_row * { - color: gray; - width: auto; - white-space: nowrap; - margin: 0 1em; -} - -.instance_name { - color: black; -} diff --git a/protocol-designer/src/components/WellToolTip.js b/protocol-designer/src/components/WellToolTip.js deleted file mode 100644 index e165bf4511a..00000000000 --- a/protocol-designer/src/components/WellToolTip.js +++ /dev/null @@ -1,40 +0,0 @@ -// @flow -import * as React from 'react' - -import styles from './WellToolTip.css' - -type Props = { - wellContent: { - name: string, - volume: number, - serialize: boolean, - wellName: string, - - concentration?: string, - ingredientNum?: number, - }, -} - -// TODO: Ian 2018-07-02 use or lose this component! -export default function WellToolTip (props: Props) { - const wellContent = props.wellContent - return ( -
-

{wellContent.name}

-
-
- {wellContent.wellName} -
- {wellContent.serialize &&
- {wellContent.name || ''} {wellContent.ingredientNum} -
} -
- {wellContent.volume} uL -
-
- {wellContent.concentration || '-'} -
-
-
- ) -} diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 02b822a0516..8f36988700e 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -51,13 +51,12 @@ class BrowseLabwareModal extends React.Component { return ( { this.wrapperRef = ref }} className={modalStyles.modal} contentsClassName={cx(modalStyles.modal_contents, modalStyles.transparent_content)} onCloseClick={this.handleClose}> - + { - ({makeHandleMouseMove, handleMouseLeaveWell, tooltipWellName}) => ( + ({makeHandleMouseOverWell, handleMouseLeaveWell, tooltipWellName}) => ( @@ -65,7 +64,7 @@ class BrowseLabwareModal extends React.Component { const color = ingredIdsToColor(well.groupIds) const mouseHandlers = color ? { - onMouseMove: makeHandleMouseMove(wellName, well.ingreds), + onMouseOver: makeHandleMouseOverWell(wellName, well.ingreds), onMouseLeave: handleMouseLeaveWell, } : {} diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 3772110e701..29792e81b46 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -20,7 +20,9 @@ import { import {getWellSetForMultichannel} from '../../well-selection/utils' import SelectionRect from '../SelectionRect.js' import type {ContentsByWell, Wells} from '../../labware-ingred/types' +import type {WellIngredientNames} from '../../steplist/types' import type {GenericRect} from '../../collision-types' +import WellTooltip from './WellTooltip' export type Props = { wellContents: ContentsByWell, @@ -31,6 +33,7 @@ export type Props = { deselectWells: (Wells) => mixed, updateHighlightedWells: (Wells) => mixed, pipetteChannels?: ?Channels, + ingredNames: WellIngredientNames, } class SelectableLabware extends React.Component { @@ -84,7 +87,8 @@ class SelectableLabware extends React.Component { } } - makeHandleMouseOverWell = (wellName: string) => () => { + makeHandleMouseOverWell = (wellName: string, callback: () => void) => () => { + callback() if (this.props.pipetteChannels === 8) { const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) const nextHighlightedWells = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) @@ -94,7 +98,8 @@ class SelectableLabware extends React.Component { } } - handleMouseExitWell = () => { + makeHandleMouseExitWell = (callback: () => void) => () => { + callback() this.props.updateHighlightedWells({}) } @@ -105,6 +110,7 @@ class SelectableLabware extends React.Component { highlightedWells, selectedWells, pipetteChannels, + ingredNames, } = this.props const allWellDefsByName = getWellDefsForSVG(containerType) @@ -125,23 +131,31 @@ class SelectableLabware extends React.Component { originYOffset={WELL_LABEL_OFFSET} onSelectionMove={this.handleSelectionMove} onSelectionDone={this.handleSelectionDone}> - - - {map(wellContents, (well, wellName) => ( - - ))} - - + + { + ({makeHandleMouseOverWell, handleMouseLeaveWell}) => ( + + + + {map(wellContents, (well, wellName) => ( + + ))} + + + + ) + } + ) } diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js index f8cc4def85c..2aac11ebc27 100644 --- a/protocol-designer/src/components/labware/WellTooltip.js +++ b/protocol-designer/src/components/labware/WellTooltip.js @@ -2,6 +2,7 @@ import * as React from 'react' import {Popper, Reference, Manager} from 'react-popper' +import cx from 'classnames' import type {LocationLiquidState} from '../../step-generation' import {PillTooltipContents} from '../steplist/SubstepRow' import type {WellIngredientNames} from '../../steplist/types' @@ -9,6 +10,8 @@ import {Portal} from '../portals/TopPortal' import styles from './labware.css' +const TOOLTIP_OFFSET = 22 + type Props = {ingredNames: WellIngredientNames} type State = { @@ -24,31 +27,17 @@ const initialState: State = { tooltipWellIngreds: null, } -const MOUSE_TOOLTIP_OFFSET_PIXELS = 14 - -type MouseTargetProps = {top: number, left: number} -class VirtualMouseTarget extends React.Component { - render () { - return ( -
- ) - } -} class WellTooltip extends React.Component { state: State = initialState mouseRef - makeHandleMouseMove = (wellName: string, wellIngreds: LocationLiquidState) => (e) => { - const {pageX, pageY} = e - if (Object.keys(wellIngreds).length > 0 && pageX && pageY) { + makeHandleMouseOverWell = (wellName: string, wellIngreds: LocationLiquidState) => (e) => { + const wellBoundingRect = e.target.getBoundingClientRect() + const {x, y, height, width} = wellBoundingRect + if (Object.keys(wellIngreds).length > 0 && x && y) { this.setState({ - tooltipX: pageX + MOUSE_TOOLTIP_OFFSET_PIXELS, - tooltipY: pageY + MOUSE_TOOLTIP_OFFSET_PIXELS, + tooltipX: x + (width / 2), + tooltipY: y + (height / 2), tooltipWellName: wellName, tooltipWellIngreds: wellIngreds, }) @@ -67,33 +56,34 @@ class WellTooltip extends React.Component { {({ref}) => ( - -
-
- ) - } + +
+
+ )}
{this.props.children({ - makeHandleMouseMove: this.makeHandleMouseMove, + makeHandleMouseOverWell: this.makeHandleMouseOverWell, handleMouseLeaveWell: this.handleMouseLeaveWell, tooltipWellName: this.state.tooltipWellName, })} {this.state.tooltipWellName && - - {({ref, style, placement}) => { - console.log('tried Render', style, placement) + + {({ref, style, placement, arrowProps}) => { return (
+
) diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index b68a33741ec..720757d5eb6 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -92,3 +92,68 @@ z-index: 10001; position: absolute; } + +.virtual_reference { + width: 1px; + height: 1px; + position: absolute; +} + +.arrow { + position: absolute; + bottom: 0; + left: 0; + margin-bottom: -0.5em; + width: 1em; + height: 0.5em; + + &::before { + border-width: 0.5em 0.5em 0 0.5em; + content: ''; + margin: auto; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: var(--c-bg-dark) transparent transparent transparent; + } +} + +.arrow.bottom { + top: 0; + left: 0; + margin-top: -0.5em; + width: 1em; + height: 0.5em; + + &::before { + border-width: 0 0.5em 0.5em 0.5em; + border-color: transparent transparent var(--c-bg-dark) transparent; + } +} + +.arrow.right { + top: 0; + left: 0; + margin-left: -0.5em; + height: 1em; + width: 0.5em; + + &::before { + border-width: 0.5em 0.5em 0.5em 0; + border-color: transparent var(--c-bg-dark) transparent transparent; + } +} + +.arrow.left { + top: 0; + right: 0; + margin-right: -0.5em; + height: 1em; + width: 0.5em; + + &::before { + border-width: 0.5em 0 0.5em 0.5em; + border-color: transparent transparent transparent var(--c-bg-dark); + } +} \ No newline at end of file From 9fad79aa8833c349a019212073af46883ad0006d Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 22 Oct 2018 13:17:23 -0400 Subject: [PATCH 06/10] feat(protocol-designer): add liquid tooltips to well selection, with popper and portal Add functionality to allow users to view contents of wells on hover via a tooltip while selecting wells to dispense or aspirate from/to in the step forms. To allow reliable positioning of tooltips in the future, these targets and references are placed inside portals that render them at the page level, unbound by any intermediate DOM element and the possibility of overly cascading styles. We can now mend the edge case rendering of large tooltips that exceed the viewport by tapping into the geometry calculations that Popper uses to flip and reposition if close to boundaries. Closes #2487 --- .../WellSelectionInput/WellSelectionModal.js | 2 +- .../components/labware/SelectableLabware.js | 28 ++++++------- .../src/components/labware/WellTooltip.js | 39 ++++++++++++------- .../src/components/labware/labware.css | 2 +- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 694a75ca730..f2f0320e010 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -15,7 +15,7 @@ import * as wellContentsSelectors from '../../../top-selectors/well-contents' import {selectors} from '../../../labware-ingred/reducers' import type {Wells, ContentsByWell} from '../../../labware-ingred/types' import {selectors as steplistSelectors} from '../../../steplist' -import type {WellIngredientNames} from '../../steplist/types' +import type {WellIngredientNames} from '../../../steplist/types' import {changeFormInput} from '../../../steplist/actions' import type {StepFieldName} from '../../../steplist/fieldLevel' import type {PipetteData} from '../../../step-generation/types' diff --git a/protocol-designer/src/components/labware/SelectableLabware.js b/protocol-designer/src/components/labware/SelectableLabware.js index 29792e81b46..ba445e9d2e6 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.js +++ b/protocol-designer/src/components/labware/SelectableLabware.js @@ -87,21 +87,23 @@ class SelectableLabware extends React.Component { } } - makeHandleMouseOverWell = (wellName: string, callback: () => void) => () => { - callback() - if (this.props.pipetteChannels === 8) { - const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) - const nextHighlightedWells = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) - nextHighlightedWells && this.props.updateHighlightedWells(nextHighlightedWells) - } else { - this.props.updateHighlightedWells({[wellName]: wellName}) + makeHandleMouseOverWell = (wellName: string, callback: (e: SyntheticMouseEvent<*>) => void) => + (e: SyntheticMouseEvent<*>) => { + callback(e) + if (this.props.pipetteChannels === 8) { + const wellSet = getWellSetForMultichannel(this.props.containerType, wellName) + const nextHighlightedWells = reduce(wellSet, (acc, well) => ({...acc, [well]: well}), {}) + nextHighlightedWells && this.props.updateHighlightedWells(nextHighlightedWells) + } else { + this.props.updateHighlightedWells({[wellName]: wellName}) + } } - } - makeHandleMouseExitWell = (callback: () => void) => () => { - callback() - this.props.updateHighlightedWells({}) - } + makeHandleMouseExitWell = (callback: (e: SyntheticMouseEvent<*>) => void) => + (e: SyntheticMouseEvent<*>) => { + callback(e) + this.props.updateHighlightedWells({}) + } render () { const { diff --git a/protocol-designer/src/components/labware/WellTooltip.js b/protocol-designer/src/components/labware/WellTooltip.js index 2aac11ebc27..c3828089ed7 100644 --- a/protocol-designer/src/components/labware/WellTooltip.js +++ b/protocol-designer/src/components/labware/WellTooltip.js @@ -12,7 +12,16 @@ import styles from './labware.css' const TOOLTIP_OFFSET = 22 -type Props = {ingredNames: WellIngredientNames} +type WellTooltipParams = { + makeHandleMouseOverWell: (wellName: string, wellIngreds: LocationLiquidState) => (e: SyntheticMouseEvent<*>) => void, + handleMouseLeaveWell: (e: SyntheticMouseEvent<*>) => void, + tooltipWellName: ?string, +} + +type Props = { + children: (WellTooltipParams) => React.Node, + ingredNames: WellIngredientNames, +} type State = { tooltipX: ?number, @@ -29,22 +38,24 @@ const initialState: State = { class WellTooltip extends React.Component { state: State = initialState - mouseRef - makeHandleMouseOverWell = (wellName: string, wellIngreds: LocationLiquidState) => (e) => { - const wellBoundingRect = e.target.getBoundingClientRect() - const {x, y, height, width} = wellBoundingRect - if (Object.keys(wellIngreds).length > 0 && x && y) { - this.setState({ - tooltipX: x + (width / 2), - tooltipY: y + (height / 2), - tooltipWellName: wellName, - tooltipWellIngreds: wellIngreds, - }) + makeHandleMouseOverWell = (wellName: string, wellIngreds: LocationLiquidState) => (e: SyntheticMouseEvent<*>) => { + const {target} = e + if (target instanceof Element) { + const wellBoundingRect = target.getBoundingClientRect() + const {left, top, height, width} = wellBoundingRect + if (Object.keys(wellIngreds).length > 0 && left && top) { + this.setState({ + tooltipX: left + (width / 2), + tooltipY: top + (height / 2), + tooltipWellName: wellName, + tooltipWellIngreds: wellIngreds, + }) + } } } - handleMouseLeaveWell = (e) => { + handleMouseLeaveWell = (e: SyntheticMouseEvent<*>) => { this.setState(initialState) } @@ -80,7 +91,7 @@ class WellTooltip extends React.Component { data-placement={placement} className={styles.tooltip_box}>
diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index 720757d5eb6..3b248ec0e8e 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -156,4 +156,4 @@ border-width: 0.5em 0 0.5em 0.5em; border-color: transparent transparent transparent var(--c-bg-dark); } -} \ No newline at end of file +} From f66756ff51b23e5bf6c188e9cad611d15e321a1e Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 22 Oct 2018 13:40:14 -0400 Subject: [PATCH 07/10] remove unused ref --- protocol-designer/src/components/labware/BrowseLabwareModal.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.js b/protocol-designer/src/components/labware/BrowseLabwareModal.js index 8f36988700e..1b81736bed1 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.js +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.js @@ -40,8 +40,6 @@ type DP = { type Props = SP & DP class BrowseLabwareModal extends React.Component { - wrapperRef: ?any - handleClose = () => { this.props.drillUp() } From a9774c6e73855c63d2063a80d89fb2f910cf6bc3 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 22 Oct 2018 14:43:11 -0400 Subject: [PATCH 08/10] copy change and extra scrollbar removal --- protocol-designer/src/components/labware/labware.css | 2 -- protocol-designer/src/components/portals/TopPortal.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/protocol-designer/src/components/labware/labware.css b/protocol-designer/src/components/labware/labware.css index 3b248ec0e8e..0e571b52640 100644 --- a/protocol-designer/src/components/labware/labware.css +++ b/protocol-designer/src/components/labware/labware.css @@ -94,8 +94,6 @@ } .virtual_reference { - width: 1px; - height: 1px; position: absolute; } diff --git a/protocol-designer/src/components/portals/TopPortal.js b/protocol-designer/src/components/portals/TopPortal.js index f4e81df06c1..9a74a61b6e2 100644 --- a/protocol-designer/src/components/portals/TopPortal.js +++ b/protocol-designer/src/components/portals/TopPortal.js @@ -20,7 +20,7 @@ export function Portal (props: Props): React.Node { const modalRootElem = getPortalElem() if (!modalRootElem) { - console.error('Confirm Modal root is not present, could not render modal') + console.error('TopPortal root is not present, could not render modal') return null } From 06ff2732a325bb947e33e600260dc2dbd9972765 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Mon, 22 Oct 2018 16:00:31 -0400 Subject: [PATCH 09/10] portal in protocol editor not App --- protocol-designer/src/components/App.js | 2 -- protocol-designer/src/components/ProtocolEditor.js | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/protocol-designer/src/components/App.js b/protocol-designer/src/components/App.js index 9dea1e08de1..141af7babcc 100644 --- a/protocol-designer/src/components/App.js +++ b/protocol-designer/src/components/App.js @@ -5,7 +5,6 @@ import { HashRouter, Route } from 'react-router-dom' import ConnectedDeckSetup from '../containers/ConnectedDeckSetup' import About from './About' import ProtocolEditor from './ProtocolEditor' -import {PortalRoot} from './portals/TopPortal' import '../css/reset.css' @@ -17,7 +16,6 @@ export default function App () { {/* TODO: Ian 2018-06-08 remove these unused routes & their components */} -
) diff --git a/protocol-designer/src/components/ProtocolEditor.js b/protocol-designer/src/components/ProtocolEditor.js index b1806d8501f..55a18624fda 100644 --- a/protocol-designer/src/components/ProtocolEditor.js +++ b/protocol-designer/src/components/ProtocolEditor.js @@ -13,6 +13,7 @@ import ConnectedNewFileModal from '../containers/ConnectedNewFileModal' import FileUploadErrorModal from './modals/FileUploadErrorModal' import AnalyticsModal from './modals/AnalyticsModal' import {PortalRoot as MainPageModalPortalRoot} from '../components/portals/MainPageModalPortal' +import {PortalRoot as TopPortalRoot} from './portals/TopPortal' import styles from './ProtocolEditor.css' @@ -24,7 +25,7 @@ export default function ProtocolEditor () { return (
- +
From 35ed5da61df5748b8cfcc4eb48535610c80f894b Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 23 Oct 2018 11:39:16 -0400 Subject: [PATCH 10/10] change name of selector from merge --- .../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 f2f0320e010..facb9f5b99b 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -126,7 +126,7 @@ function mapStateToProps (state: BaseState, ownProps: OP): SP { const timelineIdx = orderedSteps.findIndex(id => id === stepId) const allWellContentsForStep = allWellContentsForSteps[timelineIdx] const formData = steplistSelectors.getUnsavedForm(state) - const ingredNames = selectors.getIngredientNames(state) + const ingredNames = selectors.getLiquidNamesById(state) return { initialSelectedWells: formData ? formData[ownProps.name] : [],