Skip to content

Commit 430e4ef

Browse files
committed
create the top ui
1 parent 871aae1 commit 430e4ef

File tree

11 files changed

+290
-33
lines changed

11 files changed

+290
-33
lines changed

app/src/App/DesktopApp.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { ProtocolRunDetails } from '/app/pages/Desktop/Devices/ProtocolRunDetail
2929
import { RobotSettings } from '/app/pages/Desktop/Devices/RobotSettings'
3030
import { Labware } from '/app/pages/Desktop/Labware'
3131
import { ProtocolDetails } from '/app/pages/Desktop/Protocols/ProtocolDetails'
32-
import { ProtocolTimeline } from '/app/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline'
32+
import { Preview } from '../pages/Desktop/Protocols/Preview'
3333
import { ProtocolsLanding } from '/app/pages/Desktop/Protocols/ProtocolsLanding'
3434
import { useIsFlex, useRobot } from '/app/redux-resources/robots'
3535
import { OPENTRONS_USB } from '/app/redux/discovery'
@@ -80,9 +80,9 @@ export const DesktopApp = (): JSX.Element => {
8080
path: '/protocols/:protocolKey',
8181
},
8282
{
83-
Component: ProtocolTimeline,
84-
name: 'Protocol Timeline',
85-
path: '/protocols/:protocolKey/timeline',
83+
Component: Preview,
84+
name: 'Preview',
85+
path: '/protocols/:protocolKey/preview',
8686
},
8787
{
8888
Component: Labware,

app/src/App/__tests__/DesktopApp.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ProtocolsLanding } from '/app/pages/Desktop/Protocols/ProtocolsLanding'
2020
// prettier-ignore
2121
import { AlertsModal } from '/app/organisms/Desktop/Alerts/AlertsModal';
2222

23-
import { ProtocolTimeline } from '/app/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline'
23+
import { ProtocolTimeline } from '../../pages/Desktop/Protocols/Preview'
2424
import { useIsFlex } from '/app/redux-resources/robots'
2525
import { useFeatureFlag } from '/app/redux/config'
2626

app/src/assets/localization/en/top_navigation.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99
"instruments": "Instruments",
1010
"labware": "Labware",
1111
"modules": "modules",
12-
"pipettes": "pipettes",
1312
"pipettes_not_calibrated": "Please calibrate all pipettes specified in loaded protocol to proceed",
13+
"pipettes": "pipettes",
1414
"please_connect_to_a_robot": "Please connect to a robot to proceed",
1515
"please_load_a_protocol": "Please load a protocol to proceed",
16+
"preview": "Preview",
1617
"protocol_details": "Protocol Details",
1718
"protocol_runs": "Protocol Runs",
1819
"protocol_timeline": "Protocol Timeline",
1920
"protocols": "Protocols",
2021
"quick_transfer": "Quick Transfer",
2122
"robot_settings": "Robot Settings",
22-
"run": "run",
2323
"run_details": "Run Details",
24+
"run": "run",
2425
"settings": "Settings"
2526
}

app/src/organisms/Desktop/Breadcrumbs/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function BreadcrumbsComponent(): JSX.Element | null {
9494

9595
'/protocols': t('protocols'),
9696
[`/protocols/${protocolKey}`]: protocolDisplayName,
97+
[`/protocols/${protocolKey}/preview`]: t('preview'),
9798
}
9899

99100
// create an array of crumbs based on the pathname and defined names by path

app/src/organisms/Desktop/ProtocolDetails/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom'
33
import { ErrorBoundary } from 'react-error-boundary'
44
import { useTranslation } from 'react-i18next'
55
import { useDispatch, useSelector } from 'react-redux'
6+
import { useNavigate } from 'react-router-dom'
67
import { format } from 'date-fns'
78
import isEmpty from 'lodash/isEmpty'
89
import map from 'lodash/map'
@@ -21,6 +22,7 @@ import {
2122
DISPLAY_FLEX,
2223
DISPLAY_GRID,
2324
Flex,
25+
FLEX_MAX_CONTENT,
2426
Icon,
2527
JUSTIFY_CENTER,
2628
JUSTIFY_SPACE_BETWEEN,
@@ -30,13 +32,15 @@ import {
3032
OVERFLOW_WRAP_ANYWHERE,
3133
POSITION_RELATIVE,
3234
PrimaryButton,
35+
SecondaryButton,
3336
SIZE_1,
3437
SIZE_5,
3538
SPACING,
3639
Tabs,
3740
TYPOGRAPHY,
3841
} from '@opentrons/components'
3942
import {
43+
FLEX_ROBOT_TYPE,
4044
getGripperDisplayName,
4145
getModuleType,
4246
getSimplestDeckConfigForProtocol,
@@ -220,6 +224,7 @@ export function ProtocolDetails(
220224
groupedCommands,
221225
} = props
222226
const { t, i18n } = useTranslation(['protocol_details', 'shared'])
227+
const navigate = useNavigate()
223228
const enableProtocolStats = useFeatureFlag('protocolStats')
224229
const enableProtocolTimeline = useFeatureFlag('protocolTimeline')
225230
const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? []
@@ -382,6 +387,10 @@ export function ProtocolDetails(
382387
setShowChooseRobotToRunProtocolSlideout(true)
383388
}
384389

390+
const handleClickTimeline = (): void => {
391+
navigate(`/protocols/${protocolKey}/preview`)
392+
}
393+
385394
const UNKNOWN_ATTACHMENT_ERROR = `${protocolDisplayName} protocol uses
386395
instruments or modules from a future version of Opentrons software. Please update
387396
the app to the most recent version to run this protocol.`
@@ -503,11 +512,18 @@ export function ProtocolDetails(
503512
</Flex>
504513
<Flex
505514
css={css`
506-
display: ${DISPLAY_GRID};
515+
display: flex;
507516
justify-self: end;
517+
grid-gap: 4px;
508518
`}
509519
>
520+
{enableProtocolTimeline && robotType === FLEX_ROBOT_TYPE ? (
521+
<SecondaryButton onClick={handleClickTimeline}>
522+
Preview
523+
</SecondaryButton>
524+
) : null}
510525
<PrimaryButton
526+
width={FLEX_MAX_CONTENT}
511527
onClick={() => {
512528
handleRunProtocolButtonClick()
513529
}}

app/src/organisms/Desktop/ProtocolsLanding/ProtocolOverflowMenu.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { createPortal } from 'react-dom'
22
import { useTranslation } from 'react-i18next'
33
import { useDispatch } from 'react-redux'
4-
import { useNavigate } from 'react-router-dom'
54
import { css } from 'styled-components'
65

76
import {
@@ -18,15 +17,14 @@ import {
1817
useConditionalConfirm,
1918
useMenuHandleClickOutside,
2019
} from '@opentrons/components'
21-
import { FLEX_DISPLAY_NAME, FLEX_ROBOT_TYPE } from '@opentrons/shared-data'
20+
import { FLEX_DISPLAY_NAME } from '@opentrons/shared-data'
2221

2322
import { getTopPortalEl } from '/app/App/portal'
2423
import {
2524
ANALYTICS_DELETE_PROTOCOL_FROM_APP,
2625
ANALYTICS_PROTOCOL_PROCEED_TO_RUN,
2726
useTrackEvent,
2827
} from '/app/redux/analytics'
29-
import { useFeatureFlag } from '/app/redux/config'
3028
import {
3129
analyzeProtocol,
3230
removeProtocol,
@@ -56,15 +54,13 @@ export function ProtocolOverflowMenu(
5654
} = props
5755
const { mostRecentAnalysis, protocolKey } = storedProtocolData
5856
const { t } = useTranslation(['protocol_list', 'shared'])
59-
const enableProtocolTimeline = useFeatureFlag('protocolTimeline')
6057
const {
6158
menuOverlay,
6259
handleOverflowClick,
6360
showOverflowMenu,
6461
setShowOverflowMenu,
6562
} = useMenuHandleClickOutside()
6663
const dispatch = useDispatch<Dispatch>()
67-
const navigate = useNavigate()
6864
const trackEvent = useTrackEvent()
6965
const {
7066
confirm: confirmDeleteProtocol,
@@ -112,11 +108,6 @@ export function ProtocolOverflowMenu(
112108
dispatch(analyzeProtocol(protocolKey))
113109
setShowOverflowMenu(currentShowOverflowMenu => !currentShowOverflowMenu)
114110
}
115-
const handleClickTimeline: MouseEventHandler<HTMLButtonElement> = e => {
116-
e.preventDefault()
117-
navigate(`/protocols/${protocolKey}/timeline`)
118-
setShowOverflowMenu(prevShowOverflowMenu => !prevShowOverflowMenu)
119-
}
120111

121112
return (
122113
<Flex
@@ -159,11 +150,6 @@ export function ProtocolOverflowMenu(
159150
>
160151
{t('shared:reanalyze')}
161152
</MenuItem>
162-
{enableProtocolTimeline && robotType === FLEX_ROBOT_TYPE ? (
163-
<MenuItem onClick={handleClickTimeline}>
164-
{t('go_to_timeline')}
165-
</MenuItem>
166-
) : null}
167153
{robotType !== 'OT-2 Standard' ? (
168154
<MenuItem
169155
onClick={handleClickSendToOT3}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useEffect, useRef, useState } from 'react'
2+
import { ViewportListRef } from 'react-viewport-list'
3+
4+
import {
5+
constructInvariantContextFromRunCommands,
6+
getResultingTimelineFrameFromRunCommands,
7+
} from '@opentrons/step-generation'
8+
9+
import { Controls } from './Controls'
10+
11+
import type { ProtocolAnalysisOutput } from '@opentrons/shared-data'
12+
13+
const SEC_PER_FRAME = 3000
14+
15+
interface ContainerProps {
16+
analysis: ProtocolAnalysisOutput
17+
}
18+
export function Container(props: ContainerProps): JSX.Element {
19+
const { analysis } = props
20+
const { commands, robotType, liquids } = analysis
21+
22+
const [isPlaying, setIsPlaying] = useState<boolean>(false)
23+
const [currentCommandIndex, setCurrentCommandIndex] = useState<number>(0)
24+
const commandListRef = useRef<ViewportListRef>(null)
25+
26+
const currentCommandsSlice = commands.slice(0, currentCommandIndex + 1)
27+
const invariantContextFromRunCommands = constructInvariantContextFromRunCommands(
28+
commands
29+
)
30+
const { frame, invariantContext } = getResultingTimelineFrameFromRunCommands(
31+
currentCommandsSlice,
32+
invariantContextFromRunCommands
33+
)
34+
const handlePlayPause = (): void => {
35+
setIsPlaying(!isPlaying)
36+
}
37+
38+
useEffect(() => {
39+
if (isPlaying) {
40+
const intervalId = setInterval(() => {
41+
setCurrentCommandIndex(prev => {
42+
const nextIndex = prev < commands.length - 1 ? prev + 1 : 0
43+
commandListRef.current?.scrollToIndex(nextIndex)
44+
return nextIndex
45+
})
46+
}, SEC_PER_FRAME)
47+
48+
return () => {
49+
clearInterval(intervalId)
50+
}
51+
}
52+
}, [isPlaying, commands])
53+
54+
const { robotState } = frame
55+
56+
return (
57+
<Controls
58+
protocolName={analysis.metadata.protocolName}
59+
numErrors={analysis.errors.length}
60+
numCommandLength={commands.length}
61+
currentCommandIndex={currentCommandIndex}
62+
setCurrentCommandIndex={setCurrentCommandIndex}
63+
commandListRef={commandListRef}
64+
/>
65+
)
66+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { RefObject, SetStateAction } from 'react'
2+
import { ViewportListRef } from 'react-viewport-list'
3+
4+
import { Chip, Icon } from '@opentrons/components'
5+
6+
import styles from './preview.module.css'
7+
8+
interface ControlsProps {
9+
numErrors: number
10+
protocolName: string
11+
numCommandLength: number
12+
currentCommandIndex: number
13+
setCurrentCommandIndex: (value: SetStateAction<number>) => void
14+
commandListRef: RefObject<ViewportListRef>
15+
}
16+
export function Controls(props: ControlsProps): JSX.Element {
17+
const {
18+
numErrors,
19+
protocolName,
20+
numCommandLength,
21+
currentCommandIndex,
22+
commandListRef,
23+
setCurrentCommandIndex,
24+
} = props
25+
26+
return (
27+
<>
28+
<div className={styles.container}>
29+
<div className={styles.controlsContainer}>
30+
<div className={styles.allControlsInfo}>
31+
<div className={styles.controlsInfo}>
32+
<div className={styles.headingText}>{protocolName}</div>
33+
<div className={styles.maxContent}>
34+
{numErrors === 0 ? (
35+
<Chip type="success" chipSize="small" text="No errors" />
36+
) : (
37+
<Chip type="error" text={`${numErrors} errors`} />
38+
)}
39+
</div>
40+
</div>
41+
<div className={styles.buttons}>
42+
<button className={styles.fastButton}>
43+
<Icon
44+
name="play-icon"
45+
width="15px"
46+
height="20px"
47+
color="#006cfa"
48+
/>
49+
</button>
50+
<button className={styles.playButton}>
51+
<Icon
52+
name="play-icon"
53+
width="21px"
54+
height="24px"
55+
color="white"
56+
/>
57+
</button>
58+
<button className={styles.fastButton}>
59+
<Icon
60+
name="play-icon"
61+
width="15px"
62+
height="20px"
63+
color="#006cfa"
64+
/>
65+
</button>
66+
</div>
67+
</div>
68+
</div>
69+
<input
70+
type="range"
71+
min={1}
72+
max={numCommandLength}
73+
value={currentCommandIndex + 1}
74+
className={styles.rangeInput}
75+
style={{
76+
'--progress': `${
77+
((currentCommandIndex + 1) / numCommandLength) * 100
78+
}%`,
79+
}}
80+
onChange={e => {
81+
const nextIndex = Number(e.target.value) - 1
82+
setCurrentCommandIndex(nextIndex)
83+
commandListRef.current?.scrollToIndex(nextIndex)
84+
}}
85+
/>
86+
</div>
87+
</>
88+
)
89+
}

app/src/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline.tsx renamed to app/src/pages/Desktop/Protocols/Preview/index.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@ import { useEffect } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useParams } from 'react-router-dom'
44

5-
import {
6-
Box,
7-
Icon,
8-
ProtocolTimelineScrubber,
9-
SPACING,
10-
} from '@opentrons/components'
5+
import { Icon } from '@opentrons/components'
116

127
import { fetchProtocols, getStoredProtocol } from '/app/redux/protocol-storage'
138

9+
import { Container } from './Container'
10+
1411
import type { DesktopRouteParams } from '/app/App/types'
1512
import type { Dispatch, State } from '/app/redux/types'
1613

17-
export function ProtocolTimeline(): JSX.Element {
14+
export function Preview(): JSX.Element {
1815
const { protocolKey } = useParams<
1916
keyof DesktopRouteParams
2017
>() as DesktopRouteParams
@@ -28,10 +25,11 @@ export function ProtocolTimeline(): JSX.Element {
2825
}, [])
2926

3027
return storedProtocol != null && storedProtocol.mostRecentAnalysis != null ? (
31-
<Box padding={SPACING.spacing16}>
32-
<ProtocolTimelineScrubber analysis={storedProtocol.mostRecentAnalysis} />
33-
</Box>
28+
<Container analysis={storedProtocol.mostRecentAnalysis} />
3429
) : (
3530
<Icon size="8rem" name="ot-spinner" spin />
3631
)
3732
}
33+
{
34+
/* <ProtocolTimelineScrubber analysis={storedProtocol.mostRecentAnalysis} /> */
35+
}

0 commit comments

Comments
 (0)