From db2817699f40d918806e0f923bf66a09d27a0fd4 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Thu, 27 Sep 2018 17:58:57 -0400 Subject: [PATCH] refactor(protocol-designer): refactor PD labware components for readability * Simplify & clean up LabwareOnDeck, labware overlays, and related compenents & types --- components/interfaces/DeckSlot.js | 2 - components/src/deck/LabwareContainer.js | 9 +- .../components/LabwareSelectionModal/index.js | 2 +- .../src/components/labware/ClickableText.js | 2 +- .../labware/DisabledSelectSlotOverlay.js | 12 +- .../src/components/labware/LabwareOnDeck.js | 264 ++++++++---------- .../labware/NameThisLabwareOverlay.js | 23 +- .../src/containers/LabwareContainer.js | 139 +++++---- .../src/labware-ingred/actions.js | 2 +- .../src/labware-ingred/reducers/index.js | 7 +- 10 files changed, 237 insertions(+), 225 deletions(-) diff --git a/components/interfaces/DeckSlot.js b/components/interfaces/DeckSlot.js index 3dd657120073..93a96e8c09df 100644 --- a/components/interfaces/DeckSlot.js +++ b/components/interfaces/DeckSlot.js @@ -3,8 +3,6 @@ import * as React from 'react' export type DeckSlotProps = { slot: string, - width: number, - height: number, highlighted?: boolean, containerType?: string, children?: React.Node diff --git a/components/src/deck/LabwareContainer.js b/components/src/deck/LabwareContainer.js index 0837aac50f1c..79373441598e 100644 --- a/components/src/deck/LabwareContainer.js +++ b/components/src/deck/LabwareContainer.js @@ -1,5 +1,6 @@ // @flow import * as React from 'react' +import {SLOT_HEIGHT_MM, SLOT_WIDTH_MM} from '@opentrons/shared-data' import styles from './LabwareContainer.css' const defs = {roundSlotClipPath: 'roundSlotClipPath'} // TODO: import these defs instead of hard-coding in applications? Or should they be passed to children? @@ -7,14 +8,16 @@ const defs = {roundSlotClipPath: 'roundSlotClipPath'} // TODO: import these defs type Props = { x?: number, y?: number, - height: number, - width: number, + height?: number, + width?: number, highlighted?: boolean, children?: React.Node, } export default function LabwareContainer (props: Props) { - const {x, y, height, width, highlighted, children} = props + const {x, y, highlighted, children} = props + const height = props.height || SLOT_HEIGHT_MM + const width = props.width || SLOT_WIDTH_MM return ( diff --git a/protocol-designer/src/components/LabwareSelectionModal/index.js b/protocol-designer/src/components/LabwareSelectionModal/index.js index f1d0f42cc4c3..f1b42dfd7e80 100644 --- a/protocol-designer/src/components/LabwareSelectionModal/index.js +++ b/protocol-designer/src/components/LabwareSelectionModal/index.js @@ -17,7 +17,7 @@ type SP = { function mapStateToProps (state: BaseState): SP { return { - slot: labwareIngredSelectors.canAdd(state) || null, + slot: labwareIngredSelectors.selectedAddLabwareSlot(state) || null, permittedTipracks: pipetteSelectors.permittedTipracks(state), } } diff --git a/protocol-designer/src/components/labware/ClickableText.js b/protocol-designer/src/components/labware/ClickableText.js index 42c68c9d285b..5affbb28bc40 100644 --- a/protocol-designer/src/components/labware/ClickableText.js +++ b/protocol-designer/src/components/labware/ClickableText.js @@ -8,7 +8,7 @@ type Props = { height?: string | number, text?: string, iconName?: IconName, - onClick?: (e: SyntheticEvent<*>) => void, + onClick?: (e: SyntheticEvent<*>) => mixed, } const DEFAULT_HEIGHT = 15 diff --git a/protocol-designer/src/components/labware/DisabledSelectSlotOverlay.js b/protocol-designer/src/components/labware/DisabledSelectSlotOverlay.js index b5f2dacd2696..522d4d8a9715 100644 --- a/protocol-designer/src/components/labware/DisabledSelectSlotOverlay.js +++ b/protocol-designer/src/components/labware/DisabledSelectSlotOverlay.js @@ -1,17 +1,17 @@ // @flow import React from 'react' import cx from 'classnames' -import {HandleKeypress, type DeckSlot} from '@opentrons/components' +import {HandleKeypress} from '@opentrons/components' import styles from './labware.css' -type DisabledSelectSlotOverlayProps = {setMoveLabwareMode: (slot: ?DeckSlot) => void} +type DisabledSelectSlotOverlayProps = { + cancelMove: () => mixed, +} + class DisabledSelectSlotOverlay extends React.Component { - cancelMove = () => { - this.props.setMoveLabwareMode() - } render () { return ( - + diff --git a/protocol-designer/src/components/labware/LabwareOnDeck.js b/protocol-designer/src/components/labware/LabwareOnDeck.js index 6d925e6614c6..0fcb216a07d9 100644 --- a/protocol-designer/src/components/labware/LabwareOnDeck.js +++ b/protocol-designer/src/components/labware/LabwareOnDeck.js @@ -1,13 +1,4 @@ // @flow - -// On an empty slot: -// * Renders a slot on the deck -// * Renders Add Labware mouseover button -// -// On a slot with a container: -// * Renders a SelectablePlate in the slot -// * Renders Add Ingreds / Delete container mouseover buttons, and dispatches their actions - import React from 'react' import cx from 'classnames' import { @@ -20,7 +11,6 @@ import { clickOutside, type DeckSlot, } from '@opentrons/components' -import {getLabware} from '@opentrons/shared-data' import styles from './labware.css' import ClickableText from './ClickableText' @@ -30,32 +20,25 @@ import DisabledSelectSlotOverlay from './DisabledSelectSlotOverlay.js' const EnhancedNameThisLabwareOverlay = clickOutside(NameThisLabwareOverlay) -function OccupiedDeckSlotOverlay ({ +function LabwareDeckSlotOverlay ({ canAddIngreds, - containerId, - slot, - containerType, - containerName, - openIngredientSelector, - setMoveLabwareMode, - deleteContainer, + deleteLabware, + editLiquids, + moveLabwareSource, }) { return ( {canAddIngreds && openIngredientSelector(containerId)} + onClick={editLiquids} iconName='pencil' y='15%' text='Name & Liquids' /> } setMoveLabwareMode(slot)} + onClick={moveLabwareSource} iconName='cursor-move' y='40%' text='Move' /> ( - window.confirm(`Are you sure you want to permanently delete ${containerName || containerType} in slot ${slot}?`) && - deleteContainer({containerId, slot, containerType}) - )} + onClick={deleteLabware} iconName='close' y='65%' text='Delete' /> ) @@ -67,13 +50,13 @@ const labwareImages = { 'trash-box': IMG_TRASH, } -type SlotWithContainerProps = { +type SlotWithLabwareProps = { containerType: string, displayName: string, containerId: string, } -function SlotWithContainer (props: SlotWithContainerProps) { +function SlotWithLabware (props: SlotWithLabwareProps) { const {containerType, displayName, containerId} = props return ( @@ -90,152 +73,145 @@ function SlotWithContainer (props: SlotWithContainerProps) { ) } +type EmptyDestinationSlotOverlayProps = { + moveLabwareDestination: (e?: SyntheticEvent<*>) => mixed, +} +function EmptyDestinationSlotOverlay (props: EmptyDestinationSlotOverlayProps) { + const {moveLabwareDestination} = props + + const handleSelectMoveDestination = (e: SyntheticEvent<*>) => { + e.preventDefault() + moveLabwareDestination() + } + + return ( + + + + + ) +} + +type EmptyDeckSlotOverlayProps = { + addLabware: (e: SyntheticEvent<*>) => mixed, +} +function EmptyDeckSlotOverlay (props: EmptyDeckSlotOverlayProps) { + const {addLabware} = props + return ( + + + + window.alert('NOT YET IMPLEMENTED: Add Copy') /* TODO: New Copy feature */} + iconName='content-copy' y='55%' text='Add Copy' /> + + ) +} + type LabwareOnDeckProps = { slot: DeckSlot, - containerId: string, - containerType: string, containerName: ?string, - showNameOverlay: ?boolean, - - // canAdd: boolean, + containerType: string, - activeModals: { - ingredientSelection: ?{ - containerName: ?string, - slot: ?DeckSlot, - }, - labwareSelection: boolean, - }, - openIngredientSelector: (containerId: string) => void, + showNameOverlay: ?boolean, + slotHasLabware: boolean, + highlighted: boolean, - // createContainer: ({slot: string, containerType: string}) => mixed, - deleteContainer: ({containerId: string, slot: DeckSlot, containerType: string}) => void, - modifyContainer: ({containerId: string, modify: {[field: string]: mixed}}) => void, // eg modify = {name: 'newName'} + addLabwareMode: boolean, + canAddIngreds: boolean, + deckSetupMode: boolean, + moveLabwareMode: boolean, - openLabwareSelector: ({slot: DeckSlot}) => void, - // closeLabwareSelector: ({slot: string}) => mixed, + addLabware: () => mixed, + editLiquids: () => mixed, + deleteLabware: () => mixed, - setMoveLabwareMode: (slot: ?DeckSlot) => void, + cancelMove: () => mixed, + moveLabwareDestination: () => mixed, + moveLabwareSource: () => mixed, slotToMoveFrom: ?DeckSlot, - moveLabware: (slot: DeckSlot) => void, - - height?: number, - width?: number, - highlighted: boolean, - deckSetupMode: boolean, + setLabwareName: (name: ?string) => mixed, + setDefaultLabwareName: () => mixed, } - export default function LabwareOnDeck (props: LabwareOnDeckProps) { const { slot, - containerId, - containerType, containerName, - showNameOverlay, - - // canAdd, + containerType, - activeModals, - openIngredientSelector, + showNameOverlay, + slotHasLabware, + highlighted, - // createContainer, - deleteContainer, - modifyContainer, + addLabwareMode, + canAddIngreds, + deckSetupMode, + moveLabwareMode, - openLabwareSelector, - // closeLabwareSelector, + addLabware, + editLiquids, + deleteLabware, - setMoveLabwareMode, + cancelMove, + moveLabwareDestination, + moveLabwareSource, slotToMoveFrom, - moveLabware, - - height, - width, - highlighted, - deckSetupMode, + setDefaultLabwareName, + setLabwareName, } = props - const slotIsOccupied = !!containerType - - let canAddIngreds: boolean = !showNameOverlay - - // labware definition's metadata.isValueSource defaults to true, - // only use it when it's defined as false - const labwareInfo = getLabware(containerType) - if (!labwareInfo || labwareInfo.metadata.isValidSource === false) { - canAddIngreds = false + // determine what overlay to show + let overlay = null + if (deckSetupMode && !addLabwareMode) { + if (moveLabwareMode) { + overlay = (slotToMoveFrom === slot) + ? + : + } else { + if (showNameOverlay) { + overlay = + } else { + overlay = (slotHasLabware) + ? + : + } + } } - const setDefaultLabwareName = () => modifyContainer({ - containerId, - modify: {name: null}, - }) - - const handleSelectMoveDestination = (e: SyntheticEvent<*>) => { - e.preventDefault() - moveLabware(slot) - } - const cancelMove = () => { - setMoveLabwareMode() - } + const labwareOrSlot = (slotHasLabware) + ? + : return ( - - {/* The actual deck slot container: rendering of container, or rendering of empty slot */} - {slotIsOccupied - ? - : - } - - {(!deckSetupMode || activeModals.labwareSelection) - // "Add Labware" labware selection dropdown menu - ? null - : (slotToMoveFrom - // Mouseover empty slot -- Add (or Copy if in copy mode) - ? - moveLabware(slot)} /> - - - : - - openLabwareSelector({slot})} - iconName='plus' y='30%' text='Add Labware' /> - window.alert('NOT YET IMPLEMENTED: Add Copy') /* TODO: New Copy feature */} - iconName='content-copy' y='55%' text='Add Copy' /> - - ) - } - - {slotToMoveFrom === slot && - } - - {deckSetupMode && slotIsOccupied && !slotToMoveFrom && !showNameOverlay && - - } - - {deckSetupMode && showNameOverlay && - - } + + {labwareOrSlot} + {overlay} ) } diff --git a/protocol-designer/src/components/labware/NameThisLabwareOverlay.js b/protocol-designer/src/components/labware/NameThisLabwareOverlay.js index 74622ed8b2ae..443eeb1b73c9 100644 --- a/protocol-designer/src/components/labware/NameThisLabwareOverlay.js +++ b/protocol-designer/src/components/labware/NameThisLabwareOverlay.js @@ -3,15 +3,12 @@ import * as React from 'react' import ForeignDiv from '../../components/ForeignDiv.js' import ClickableText from './ClickableText' import styles from './labware.css' -import type {ClickOutsideInterface, DeckSlot} from '@opentrons/components' +import type {ClickOutsideInterface} from '@opentrons/components' type Props = { - containerType: string, - containerId: string, - slot: DeckSlot, + setLabwareName: (name: ?string) => mixed, // TODO Ian 2018-02-16 type these fns elsewhere and import the type - modifyContainer: (args: {containerId: string, modify: {[field: string]: mixed}}) => void, - deleteContainer: (args: {containerId: string, slot: DeckSlot, containerType: string}) => void, + deleteLabware: () => mixed, } & ClickOutsideInterface type State = { @@ -44,21 +41,13 @@ export default class NameThisLabwareOverlay extends React.Component { - const { containerId, modifyContainer } = this.props const containerName = this.state.inputValue || null - - modifyContainer({ - containerId, - modify: { name: containerName }, - }) + this.props.setLabwareName(containerName) } render () { const { - containerType, - containerId, - slot, - deleteContainer, + deleteLabware, passRef, } = this.props @@ -79,7 +68,7 @@ export default class NameThisLabwareOverlay extends React.Component - deleteContainer({containerId, slot, containerType})} + diff --git a/protocol-designer/src/containers/LabwareContainer.js b/protocol-designer/src/containers/LabwareContainer.js index 77725c97691e..76f9557cb0da 100644 --- a/protocol-designer/src/containers/LabwareContainer.js +++ b/protocol-designer/src/containers/LabwareContainer.js @@ -2,17 +2,15 @@ import * as React from 'react' import {connect} from 'react-redux' -import {getLabware} from '@opentrons/shared-data' +import {getLabware, getIsTiprack} from '@opentrons/shared-data' import {selectors} from '../labware-ingred/reducers' import { openIngredientSelector, - createContainer, deleteContainer, modifyContainer, - openLabwareSelector, - closeLabwareSelector, + openAddLabwareModal, setMoveLabwareMode, moveLabware, @@ -23,74 +21,121 @@ import {LabwareOnDeck} from '../components/labware' import type {BaseState} from '../types' import type {DeckSlot} from '@opentrons/components' -type OwnProps = { +type OP = { slot: DeckSlot, } type Props = React.ElementProps -type DispatchProps = { - createContainer: mixed, - deleteContainer: mixed, - modifyContainer: mixed, +type DP = { + addLabware: () => mixed, + editLiquids: () => mixed, + deleteLabware: () => mixed, - openIngredientSelector: mixed, - openLabwareSelector: mixed, + cancelMove: () => mixed, + moveLabwareDestination: () => mixed, + moveLabwareSource: () => mixed, - closeLabwareSelector: mixed, - - setMoveLabwareMode: mixed, - moveLabware: mixed, + setLabwareName: (name: ?string) => mixed, + setDefaultLabwareName: () => mixed, } -type StateProps = $Diff +type SP = $Diff -function mapStateToProps (state: BaseState, ownProps: OwnProps): StateProps { +function mapStateToProps (state: BaseState, ownProps: OP): SP { const {slot} = ownProps const container = selectors.containersBySlot(state)[ownProps.slot] const labwareNames = selectors.getLabwareNames(state) - const containerInfo = (container) - ? {containerType: container.type, containerId: container.id, containerName: labwareNames[container.id]} - : {} + + const containerType = container && container.type + const containerId = container && container.id + const containerName = containerId && labwareNames[containerId] const selectedContainer = selectors.getSelectedContainer(state) const isSelectedSlot = !!(selectedContainer && selectedContainer.slot === slot) const deckSetupMode = steplistSelectors.getSelectedTerminalItemId(state) === START_TERMINAL_ITEM_ID - const labwareHasName = container && selectors.getSavedLabware(state)[container.id] - const labwareData = container && getLabware(container.type) - // TODO: Ian 2018-07-10 use shared-data accessor - const isTiprack = labwareData && labwareData.metadata.isTiprack + const labwareHasName = container && selectors.getSavedLabware(state)[containerId] + const isTiprack = getIsTiprack(containerType) + const showNameOverlay = container && !isTiprack && !labwareHasName + + // ===================== + const slotToMoveFrom = selectors.slotToMoveFrom(state) + const activeModals = selectors.activeModals(state) + + const slotHasLabware = !!containerType + const addLabwareMode = activeModals.labwareSelection + const moveLabwareMode = Boolean(slotToMoveFrom) + + const setDefaultLabwareName = () => modifyContainer({ + containerId, + modify: {name: null}, + }) + + // labware definition's metadata.isValueSource defaults to true, + // only use it when it's defined as false + let canAddIngreds: boolean = !showNameOverlay + const labwareInfo = getLabware(containerType) + if (!labwareInfo || labwareInfo.metadata.isValidSource === false) { + canAddIngreds = false + } return { - ...containerInfo, - slot, - showNameOverlay: container && !isTiprack && !labwareHasName, - canAdd: selectors.canAdd(state), - activeModals: selectors.activeModals(state), - slotToMoveFrom: selectors.slotToMoveFrom(state), + slotHasLabware, + addLabwareMode, + moveLabwareMode, + setDefaultLabwareName, + canAddIngreds, + labwareInfo, + + showNameOverlay, + slotToMoveFrom, highlighted: (deckSetupMode) - // in deckSetupMode, labware is highlighted when selected (currently editing ingredients) - // or when targeted by an open "Add Labware" modal - ? (isSelectedSlot || selectors.canAdd(state) === slot) - // outside of deckSetupMode, labware is highlighted when step/substep is hovered - : steplistSelectors.hoveredStepLabware(state).includes(container && container.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, + + slot, + containerName, + containerType, + containerId, } } -const mapDispatchToProps = { - createContainer, - deleteContainer, - modifyContainer, - - openIngredientSelector, - openLabwareSelector, - - closeLabwareSelector, +function mergeProps (stateProps: SP, dispatchProps: {dispatch: Dispatch<*>}, ownProps: OP): Props { + const {slot} = ownProps + const {dispatch} = dispatchProps + const {containerId, containerName, containerType} = stateProps + + const actions = { + addLabware: () => dispatch(openAddLabwareModal({slot})), + editLiquids: () => dispatch(openIngredientSelector(containerId)), + deleteLabware: () => ( + window.confirm(`Are you sure you want to permanently delete ${containerName || containerType} in slot ${slot}?`) && + dispatch(deleteContainer({containerId, slot, containerType})) + ), + + cancelMove: () => dispatch(setMoveLabwareMode()), + moveLabwareDestination: () => dispatch(moveLabware(slot)), + moveLabwareSource: () => dispatch(setMoveLabwareMode(slot)), + + setLabwareName: (name: ?string) => dispatch(modifyContainer({ + containerId, + modify: {name}, + })), + setDefaultLabwareName: () => dispatch(modifyContainer({ + containerId, + modify: {name: null}, + })), + } - setMoveLabwareMode, - moveLabware, + return { + ...stateProps, + ...actions, + } } -export default connect(mapStateToProps, mapDispatchToProps)(LabwareOnDeck) +export default connect(mapStateToProps, null, mergeProps)(LabwareOnDeck) diff --git a/protocol-designer/src/labware-ingred/actions.js b/protocol-designer/src/labware-ingred/actions.js index 347e8a149187..b2b9b706a3ad 100644 --- a/protocol-designer/src/labware-ingred/actions.js +++ b/protocol-designer/src/labware-ingred/actions.js @@ -12,7 +12,7 @@ import type {DeckSlot} from '@opentrons/components' // ===== Labware selector actions ===== -export const openLabwareSelector = createAction( +export const openAddLabwareModal = createAction( 'OPEN_LABWARE_SELECTOR', (args: {slot: DeckSlot}) => args ) diff --git a/protocol-designer/src/labware-ingred/reducers/index.js b/protocol-designer/src/labware-ingred/reducers/index.js index a375828f845d..de6abe2a3cc7 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.js +++ b/protocol-designer/src/labware-ingred/reducers/index.js @@ -48,7 +48,7 @@ const nextEmptySlot = loadedContainersSubstate => { // modeLabwareSelection: boolean. If true, we're selecting labware to add to a slot // (this state just toggles a modal) const modeLabwareSelection = handleActions({ - OPEN_LABWARE_SELECTOR: (state, action: ActionType) => + OPEN_LABWARE_SELECTOR: (state, action: ActionType) => action.payload.slot, CLOSE_LABWARE_SELECTOR: () => false, CREATE_CONTAINER: () => false, @@ -363,7 +363,8 @@ const disposalLabwareOptions: Selector = createSelector( }, []) ) -const canAdd = (state: BaseState) => rootSelector(state).modeLabwareSelection // false or selected slot to add labware to, eg 'A2' +// false or selected slot to add labware to, eg 'A2' +const selectedAddLabwareSlot = (state: BaseState) => rootSelector(state).modeLabwareSelection const getSavedLabware = (state: BaseState) => rootSelector(state).savedLabware @@ -470,7 +471,7 @@ export const selectors = { allIngredientNamesIds, loadedContainersBySlot, containersBySlot, - canAdd, + selectedAddLabwareSlot, disposalLabwareOptions, labwareOptions, hasLiquid,