diff --git a/app/src/components/ConnectModulesModal/Prompt.js b/app/src/components/ConnectModulesModal/Prompt.js
index 882f513c05d..6a24c618f08 100644
--- a/app/src/components/ConnectModulesModal/Prompt.js
+++ b/app/src/components/ConnectModulesModal/Prompt.js
@@ -25,15 +25,19 @@ const connectedAlertProps = {
export default function Prompt (props: Props) {
const {modulesMissing, onClick} = props
+
const alert = modulesMissing
?
:
+
const message = modulesMissing
- ? 'Plug in and power up the required module via USB to your robot.'
- : 'Module succesfully detected.'
+ ? 'Plug in and power up the required module(s) via USB to your robot.'
+ : 'Module(s) successfully detected.'
+
const buttonText = modulesMissing
- ? 'try searching for missing module'
+ ? 'try searching for missing module(s)'
: 'continue to labware setup'
+
return (
{alert}
diff --git a/app/src/components/ConnectModulesModal/ReviewModuleItem.js b/app/src/components/ConnectModulesModal/ReviewModuleItem.js
new file mode 100644
index 00000000000..50268b4a985
--- /dev/null
+++ b/app/src/components/ConnectModulesModal/ReviewModuleItem.js
@@ -0,0 +1,53 @@
+// @flow
+import * as React from 'react'
+import {connect} from 'react-redux'
+import countBy from 'lodash/countBy'
+
+import {makeGetRobotModules} from '../../http-api-client'
+import {selectors as robotSelectors} from '../../robot'
+import {ModuleItem} from '../DeckMap'
+
+import type {LabwareComponentProps} from '@opentrons/components'
+import type {State} from '../../types'
+import type {SessionModule} from '../../robot'
+
+type OP = LabwareComponentProps
+
+type Props = {
+ module: ?SessionModule,
+ present: boolean
+}
+
+export default connect(makeMapStateToProps)(ReviewModuleItem)
+
+function ReviewModuleItem (props: Props) {
+ if (!props.module) return null
+
+ return (
+
+ )
+}
+
+function makeMapStateToProps (): (state: State, ownProps: OP) => Props {
+ // TODO(mc, 2018-07-23): this logic is duplicated because can only get props
+ // into Deck.props.LabwareComponent via redux
+ const getRobotModules = makeGetRobotModules()
+
+ return (state, ownProps) => {
+ const robot = robotSelectors.getConnectedRobot(state)
+ const module = robotSelectors.getModulesBySlot(state)[ownProps.slot]
+ const sessionModules = robotSelectors.getModules(state)
+ const actualModulesCall = robot && getRobotModules(state, robot)
+ const actualModules =
+ actualModulesCall &&
+ actualModulesCall.response &&
+ actualModulesCall.response.modules
+
+ const requiredNames = countBy(sessionModules, 'name')
+ const actualNames = countBy(actualModules || [], 'name')
+ const present =
+ !module || requiredNames[module.name] === actualNames[module.name]
+
+ return {present, module}
+ }
+}
diff --git a/app/src/components/ConnectModulesModal/index.js b/app/src/components/ConnectModulesModal/index.js
index 80db82c9003..21abc2b7898 100644
--- a/app/src/components/ConnectModulesModal/index.js
+++ b/app/src/components/ConnectModulesModal/index.js
@@ -1,17 +1,89 @@
// @flow
import * as React from 'react'
+import {connect} from 'react-redux'
+import countBy from 'lodash/countBy'
+
+import {makeGetRobotModules, fetchModules} from '../../http-api-client'
+import {selectors as robotSelectors, actions as robotActions} from '../../robot'
+
+import {Deck} from '@opentrons/components'
+import {RefreshWrapper} from '../Page'
import {Modal} from '../modals'
import Prompt from './Prompt'
+import ReviewModuleItem from './ReviewModuleItem'
+
+import type {State} from '../../types'
+import type {RobotService, SessionModule} from '../../robot'
+import type {Module} from '../../http-api-client'
-type Props = {
- modulesMissing: boolean,
- onClick: () => mixed,
+type OP = {
+ robot: RobotService
}
-export default function ConnectModulesModal (props: Props) {
+type SP = {
+ modulesRequired: boolean,
+ modulesMissing: boolean
+}
+
+type DP = {
+ setReviewed: () => mixed,
+ fetchModules: () => mixed
+}
+
+type Props = OP & SP & DP
+
+export default connect(
+ makeMapStateToProps,
+ mapDispatchToProps
+)(ConnectModulesModal)
+
+function ConnectModulesModal (props: Props) {
+ if (!props.modulesRequired) return null
+
+ const {modulesMissing, setReviewed, fetchModules} = props
+ const onPromptClick = modulesMissing ? fetchModules : setReviewed
+
return (
-
-
-
+
+
+
+
+
+
+ )
+}
+
+function makeMapStateToProps (): (state: State, ownProps: OP) => SP {
+ const getRobotModules = makeGetRobotModules()
+
+ return (state, ownProps) => {
+ const sessionModules = robotSelectors.getModules(state)
+ const actualModulesCall = getRobotModules(state, ownProps.robot)
+ const actualModules =
+ actualModulesCall.response && actualModulesCall.response.modules
+
+ return {
+ modulesRequired: sessionModules.length !== 0,
+ modulesMissing: checkModulesMissing(sessionModules, actualModules)
+ }
+ }
+}
+
+function mapDispatchToProps (dispatch: Dispatch, ownProps: OP): DP {
+ return {
+ setReviewed: () => dispatch(robotActions.setModulesReviewed(true)),
+ fetchModules: () => dispatch(fetchModules(ownProps.robot))
+ }
+}
+
+function checkModulesMissing (
+ required: Array
,
+ actual: ?Array
+): boolean {
+ const requiredNames = countBy(required, 'name')
+ const actualNames = countBy(actual, 'name')
+
+ return Object.keys(requiredNames).some(
+ n => requiredNames[n] !== actualNames[n]
)
}
diff --git a/app/src/components/ConnectModulesModal/styles.css b/app/src/components/ConnectModulesModal/styles.css
index aa8ebda7282..2422c32351b 100644
--- a/app/src/components/ConnectModulesModal/styles.css
+++ b/app/src/components/ConnectModulesModal/styles.css
@@ -15,14 +15,14 @@
@apply --font-header-light;
font-weight: normal;
- margin-bottom: 1rem;
+ margin-top: 1rem;
text-align: center;
}
.prompt_button {
display: block;
width: auto;
- margin: 1rem auto;
+ margin: 1.5rem auto;
padding-left: 3rem;
padding-right: 3rem;
}
diff --git a/app/src/components/DeckMap/ConnectedSlotItem.js b/app/src/components/DeckMap/ConnectedSlotItem.js
index 7dc9fbcea8c..413d942ec75 100644
--- a/app/src/components/DeckMap/ConnectedSlotItem.js
+++ b/app/src/components/DeckMap/ConnectedSlotItem.js
@@ -8,7 +8,8 @@ import type {State, Dispatch} from '../../types'
import {
selectors as robotSelectors,
actions as robotActions,
- type Mount
+ type Mount,
+ type SessionModule
} from '../../robot'
import type {LabwareComponentProps} from '@opentrons/components'
@@ -20,14 +21,14 @@ type OP = LabwareComponentProps & {match: Match}
type SP = {
_calibrator?: ?Mount,
_labware?: $PropertyType,
- module?: {}
+ module?: SessionModule
}
type DP = {dispatch: Dispatch}
type Props = LabwareComponentProps & {
labware?: $PropertyType,
- module?: {},
+ module?: SessionModule,
}
export default withRouter(connect(mapStateToProps, null, mergeProps)(SlotItem))
@@ -38,7 +39,7 @@ function SlotItem (props: Props) {
return (
{module && (
-
+
)}
{labware && (
+
+
+ )
+}
+
+function ModuleItemContents (props: ModuleItemProps) {
+ if (!props.review) {
+ return (
-
+ )
+ }
+
+ // TODO(mc, 2018-07-23): displayName?
+ const {present, module: {name}} = props
+ const message = present
+ ? ({name})
+ : (
+
+ Missing:
+ {name}
+
+ )
+
+ const iconClassName = cx(styles.module_review_icon, {
+ [styles.module_review_icon_present]: present
+ })
+
+ return (
+
+
+
+
)
}
diff --git a/app/src/components/DeckMap/index.js b/app/src/components/DeckMap/index.js
index c2b6adbd9a6..10f3c35467f 100644
--- a/app/src/components/DeckMap/index.js
+++ b/app/src/components/DeckMap/index.js
@@ -3,7 +3,8 @@ import React from 'react'
import {Deck} from '@opentrons/components'
import ConnectedSlotItem from './ConnectedSlotItem'
-
+import ModuleItem from './ModuleItem'
+import LabwareItem from './LabwareItem'
import styles from './styles.css'
export default function DeckMap () {
@@ -15,3 +16,7 @@ export default function DeckMap () {
/>
)
}
+
+export {LabwareItem, ModuleItem}
+export type {LabwareItemProps} from './LabwareItem'
+export type {ModuleItemProps} from './ModuleItem'
diff --git a/app/src/components/DeckMap/styles.css b/app/src/components/DeckMap/styles.css
index 88b728e1c04..ebd18fe5d7f 100644
--- a/app/src/components/DeckMap/styles.css
+++ b/app/src/components/DeckMap/styles.css
@@ -22,3 +22,20 @@
.module_icon {
color: var(--c-white);
}
+
+.module_review_icon {
+ color: var(--c-orange);
+}
+
+.module_review_icon_present {
+ color: var(--c-green);
+}
+
+.module_review_text {
+ fill: currentColor;
+ font-size: 0.5rem;
+ font-weight: var(--fw-semibold);
+ color: var(--c-white);
+ text-transform: uppercase;
+ white-space: pre;
+}
diff --git a/app/src/components/InstrumentSettings/AttachedModulesCard.js b/app/src/components/InstrumentSettings/AttachedModulesCard.js
index 467560b41af..4e11e9f53bd 100644
--- a/app/src/components/InstrumentSettings/AttachedModulesCard.js
+++ b/app/src/components/InstrumentSettings/AttachedModulesCard.js
@@ -26,29 +26,8 @@ type Props = OP & SP & DP
const TITLE = 'Modules'
-// TODO(mc, 2018-07-19): remove this testing code when API returns modules
-// const STUBBED_MODULE_DATA = [
-// {
-// name: 'tempdeck',
-// model: 'temp_deck',
-// serial: '123123124',
-// fwVersion: '1.2.13',
-// status: '86',
-// displayName: 'Temperature Module'
-// },
-// {
-// name: 'magdeck',
-// model: 'mag_deck',
-// serial: '123123124',
-// fwVersion: '1.2.13',
-// status: 'disengaged',
-// displayName: 'Magnetic Bead Module'
-// }
-// ]
-
export default connect(makeSTP, DTP)(AttachedModulesCard)
-// TODO (ka 2018-6-29): change this to a refresh card once we have endpoints
function AttachedModulesCard (props: Props) {
if (props.modulesFlag) {
return (
@@ -76,7 +55,7 @@ function makeSTP (): (state: State, ownProps: OP) => SP {
return {
modulesFlag: getModulesOn(state),
- modules: modules /* || STUBBED_MODULE_DATA */,
+ modules: modules,
refreshing: modulesCall.inProgress
}
}
diff --git a/app/src/components/ModuleItem/ModuleImage.js b/app/src/components/ModuleItem/ModuleImage.js
index 4598f34d424..3d53e2782ab 100644
--- a/app/src/components/ModuleItem/ModuleImage.js
+++ b/app/src/components/ModuleItem/ModuleImage.js
@@ -20,8 +20,7 @@ function getModuleImg (name: string) {
return MODULE_IMGS[name]
}
-// TODO (ka 2018-7-10): replace with design assets onces available
const MODULE_IMGS = {
- 'temp_deck': require('./images/module-temp@3x.png'),
- 'mag_deck': require('./images/module-mag@3x.png')
+ tempdeck: require('./images/module-temp@3x.png'),
+ magdeck: require('./images/module-mag@3x.png')
}
diff --git a/app/src/components/ReviewDeckModal/LabwareComponent.js b/app/src/components/ReviewDeckModal/LabwareComponent.js
index 533cd98e4ca..8b0d8ffdadb 100644
--- a/app/src/components/ReviewDeckModal/LabwareComponent.js
+++ b/app/src/components/ReviewDeckModal/LabwareComponent.js
@@ -3,17 +3,18 @@
import * as React from 'react'
import {connect} from 'react-redux'
-import {selectors as robotSelectors} from '../../robot'
+import {selectors as robotSelectors, type SessionModule} from '../../robot'
import type {LabwareComponentProps} from '@opentrons/components'
-import LabwareItem, {type LabwareItemProps} from '../DeckMap/LabwareItem'
-import ModuleItem from '../DeckMap/ModuleItem'
+import type {LabwareItemProps} from '../DeckMap'
+
+import {LabwareItem, ModuleItem} from '../DeckMap'
type OP = LabwareComponentProps
type SP = {
labware: ?$PropertyType,
- module: ?{}
+ module: ?SessionModule
}
type Props = OP & SP
@@ -24,7 +25,7 @@ function LabwareComponent (props: Props) {
return (
{props.module && (
-
+
)}
{props.labware && (
= createSelector(
getRobotApiState,
(state) => state[MODULES] || {inProgress: false}
+ // TODO(mc, 2018-07-23): DEBUG code; remove soon
+ // () => ({
+ // inProgress: false,
+ // error: null,
+ // request: null,
+ // response: {
+ // modules: [
+ // {
+ // name: 'tempdeck',
+ // model: 'temp_deck',
+ // serial: '123123124',
+ // fwVersion: '1.2.13',
+ // status: '86',
+ // displayName: 'Temperature Module'
+ // },
+ // {
+ // name: 'magdeck',
+ // model: 'mag_deck',
+ // serial: '123123124',
+ // fwVersion: '1.2.13',
+ // status: 'disengaged',
+ // displayName: 'Magnetic Bead Module'
+ // }
+ // ]
+ // }
+ // })
)
return selector
diff --git a/app/src/pages/Calibrate/Labware.js b/app/src/pages/Calibrate/Labware.js
index 7f9606ad578..f93cb2231a2 100644
--- a/app/src/pages/Calibrate/Labware.js
+++ b/app/src/pages/Calibrate/Labware.js
@@ -4,23 +4,21 @@ import * as React from 'react'
import {connect} from 'react-redux'
import {Route, Redirect, withRouter, type Match} from 'react-router'
import {push} from 'react-router-redux'
-import countBy from 'lodash/countBy'
import type {State, Dispatch} from '../../types'
-import type {Labware, Robot, SessionModule} from '../../robot'
-import {
- selectors as robotSelectors,
- actions as robotActions
-} from '../../robot'
+import type {Labware, Robot} from '../../robot'
+
+import {selectors as robotSelectors} from '../../robot'
+import {makeGetRobotSettings} from '../../http-api-client'
import {getModulesOn} from '../../config'
-import type {Module} from '../../http-api-client'
-import {makeGetRobotSettings, makeGetRobotModules, fetchModules} from '../../http-api-client'
-import Page, {RefreshWrapper} from '../../components/Page'
+
+import Page from '../../components/Page'
import CalibrateLabware from '../../components/CalibrateLabware'
import SessionHeader from '../../components/SessionHeader'
import ReviewDeckModal from '../../components/ReviewDeckModal'
import ConfirmModal from '../../components/CalibrateLabware/ConfirmModal'
import ConnectModulesModal from '../../components/ConnectModulesModal'
+
type OP = {
match: Match
}
@@ -30,48 +28,36 @@ type SP = {
deckPopulated: boolean,
labware: ?Labware,
calibrateToBottom: boolean,
- _robot: ?Robot,
- modulesMissing: boolean,
+ robot: ?Robot,
reviewModules: ?boolean,
}
-type DP = {dispatch: Dispatch}
+type DP = {onBackClick: () => mixed}
-type Props = SP & OP & {
- onBackClick: () => mixed,
- fetchModules: () => mixed,
- onReviewPromptClick: () => mixed,
-}
+type Props = OP & SP & DP
-export default withRouter(connect(makeMapStateToProps, null, mergeProps)(SetupDeckPage))
+export default withRouter(
+ connect(makeMapStateToProps, mapDispatchToProps)(SetupDeckPage)
+)
function SetupDeckPage (props: Props) {
const {
+ robot,
calibrateToBottom,
labware,
deckPopulated,
- modulesMissing,
reviewModules,
onBackClick,
- fetchModules,
- onReviewPromptClick,
match: {url, params: {slot}}
} = props
return (
-
- )}}
- >
+
+ )}}>
- {reviewModules && (
-
+ {robot && reviewModules && (
+
)}
{(!deckPopulated && !reviewModules) && (
@@ -87,77 +73,47 @@ function SetupDeckPage (props: Props) {
)
}} />
-
+
)
}
function makeMapStateToProps (): (state: State, ownProps: OP) => SP {
const getRobotSettings = makeGetRobotSettings()
- const getRobotModules = makeGetRobotModules()
return (state, ownProps) => {
const {match: {url, params: {slot}}} = ownProps
const labware = robotSelectors.getLabware(state)
const currentLabware = labware.find((lw) => lw.slot === slot)
- const _robot = robotSelectors.getConnectedRobot(state)
+ const robot = robotSelectors.getConnectedRobot(state)
// TODO(mc, 2018-07-19): API selector for getting the response directly
- const settingsResponse = _robot && getRobotSettings(state, _robot).response
- const settings = settingsResponse && settingsResponse.settings
- const flag = !!settings && settings.find((s) => s.id === 'calibrateToBottom')
- const calibrateToBottom = !!flag && flag.value
-
- const modules = robotSelectors.getModules(state)
+ const settingsResp = robot && getRobotSettings(state, robot).response
+ const settings = settingsResp && settingsResp.settings
- const modulesCall = _robot && getRobotModules(state, _robot)
- const modulesResponse = modulesCall && modulesCall.response
- const actualModules = modulesResponse && modulesResponse.modules
+ // TODO(mc, 2018-07-23): make diagram component a container
+ const calToBottomFlag = settings && settings.find(s => s.id === 'calibrateToBottom')
+ const calibrateToBottom = !!calToBottomFlag && calToBottomFlag.value
+ const modulesEnabled = getModulesOn(state)
const modulesReviewed = robotSelectors.getModulesReviewed(state)
- const modulesFlag = getModulesOn(state)
- const modulesRequired = modules[0]
-
- const reviewModules = modulesFlag && !modulesReviewed && !!modulesRequired
+ const reviewModules = modulesEnabled && !modulesReviewed
return {
slot,
url,
calibrateToBottom,
reviewModules,
- _robot,
- modulesMissing: checkModules(modules, actualModules),
+ robot,
deckPopulated: !!robotSelectors.getDeckPopulated(state),
labware: currentLabware
}
}
}
-function mergeProps (stateProps: SP, dispatchProps: DP, ownProps: OP): Props {
+function mapDispatchToProps (dispatch: Dispatch, ownProps: OP): DP {
const {match: {url}} = ownProps
- const {dispatch} = dispatchProps
- const {_robot, modulesMissing} = stateProps
-
- const fetchMods = () => _robot && dispatch(fetchModules(_robot))
- const onReviewPromptClick = modulesMissing
- ? fetchMods
- : () => dispatch(robotActions.setModulesReviewed(true))
return {
- ...stateProps,
- ...ownProps,
- onReviewPromptClick,
- fetchModules: fetchMods,
onBackClick: () => dispatch(push(url))
}
}
-
-function checkModules (
- required: Array,
- actual: ?Array
-): boolean {
- const requiredNames = countBy(required, 'name')
- const actualNames = countBy(actual, 'name')
-
- return Object.keys(requiredNames)
- .some(n => requiredNames[n] !== actualNames[n])
-}
diff --git a/app/src/robot/types.js b/app/src/robot/types.js
index 6076a56eef9..3b2a847f6cf 100644
--- a/app/src/robot/types.js
+++ b/app/src/robot/types.js
@@ -140,7 +140,7 @@ export type SessionModule = {
// slot module is installed in
slot: Slot,
// name identifier of the module
- name: ModuleType
+ name: ModuleType,
}
export type SessionStatus =
diff --git a/components/src/CenteredTextSvg.js b/components/src/CenteredTextSvg.js
index 6668e712738..403a956dba8 100644
--- a/components/src/CenteredTextSvg.js
+++ b/components/src/CenteredTextSvg.js
@@ -2,13 +2,14 @@
import * as React from 'react'
type CenteredTextSvgProps = {
- text: string,
+ text: React.Node,
className?: string
}
export function CenteredTextSvg (props: CenteredTextSvgProps) {
const { text, className } = props
+ // TODO(mc, 2018-07-23): add `fill='currentColor'`
return (
+ {/* TODO(mc, 2018-07-23): use props.children */}
{text}
)