diff --git a/src/web/common.css b/src/web/common.css index 805d7a156c..5edf6b8900 100644 --- a/src/web/common.css +++ b/src/web/common.css @@ -31,6 +31,7 @@ --transparent-blue: #2196F355; --orange: #d24726; --black: #1E1E1E; + --light-gray: #BBBBBB; --gray: #888888; --separator: #80808059; --focus-ring: #0E639CCC; diff --git a/src/web/traceViewer/ui/actionList.css b/src/web/traceViewer/ui/actionList.css index a98a8a2404..6947fe7ccf 100644 --- a/src/web/traceViewer/ui/actionList.css +++ b/src/web/traceViewer/ui/actionList.css @@ -14,7 +14,11 @@ limitations under the License. */ -.action-list { +.action-list-title { + padding: 0 10px; +} + +.action-list-content { display: flex; flex-direction: column; flex: auto; @@ -23,6 +27,7 @@ color: #555; overflow: auto; outline: none; + padding: 5px 0 0 5px; } .action-entry { @@ -35,15 +40,22 @@ padding-left: 3px; } +.action-entry.highlighted, .action-entry.selected { color: white; background-color: var(--gray); } -.action-list:focus .action-entry.selected { +.action-entry.highlighted { + color: white; + background-color: var(--light-gray); +} + +.action-list-content:focus .action-entry.selected { background-color: var(--blue); } +.action-entry.highlighted > div, .action-entry.selected > div { color: white; } diff --git a/src/web/traceViewer/ui/actionList.tsx b/src/web/traceViewer/ui/actionList.tsx index 0105d9d226..f4c7ec330c 100644 --- a/src/web/traceViewer/ui/actionList.tsx +++ b/src/web/traceViewer/ui/actionList.tsx @@ -16,6 +16,7 @@ import { ActionEntry } from '../../../server/trace/viewer/traceModel'; import './actionList.css'; +import './tabbedPane.css'; import * as React from 'react'; export interface ActionListProps { @@ -33,39 +34,57 @@ export const ActionList: React.FC = ({ onSelected = () => {}, onHighlighted = () => {}, }) => { - return
{ - if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') - return; - const index = selectedAction ? actions.indexOf(selectedAction) : -1; - if (event.key === 'ArrowDown') { - if (index === -1) - onSelected(actions[0]); - else - onSelected(actions[Math.min(index + 1, actions.length - 1)]); - } - if (event.key === 'ArrowUp') { - if (index === -1) - onSelected(actions[actions.length - 1]); - else - onSelected(actions[Math.max(index - 1, 0)]); - } - }} - >{actions.map((actionEntry, index) => { - const { metadata, actionId } = actionEntry; - return
onSelected(actionEntry)} - onMouseEnter={() => onHighlighted(actionEntry)} - onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)} + const actionListRef = React.createRef(); + + React.useEffect(() => { + actionListRef.current?.focus(); + }, [selectedAction]); + + return
+
+
+
Actions
+
+
+
{ + if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') + return; + const index = selectedAction ? actions.indexOf(selectedAction) : -1; + if (event.key === 'ArrowDown') { + if (index === -1) + onSelected(actions[0]); + else + onSelected(actions[Math.min(index + 1, actions.length - 1)]); + } + if (event.key === 'ArrowUp') { + if (index === -1) + onSelected(actions[actions.length - 1]); + else + onSelected(actions[Math.max(index - 1, 0)]); + } + }} + ref={actionListRef} > - ; - })}
; + {actions.map(actionEntry => { + const { metadata, actionId } = actionEntry; + const selectedSuffix = actionEntry === selectedAction ? ' selected' : ''; + const highlightedSuffix = actionEntry === highlightedAction ? ' highlighted' : ''; + return
onSelected(actionEntry)} + onMouseEnter={() => onHighlighted(actionEntry)} + onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)} + > + ; + })} +
+
; }; diff --git a/src/web/traceViewer/ui/logsTab.css b/src/web/traceViewer/ui/logsTab.css index f1d98f4c6c..01b3c9dc37 100644 --- a/src/web/traceViewer/ui/logsTab.css +++ b/src/web/traceViewer/ui/logsTab.css @@ -20,6 +20,7 @@ white-space: pre; overflow: auto; padding-top: 3px; + user-select: text; } .log-line { diff --git a/src/web/traceViewer/ui/snapshotTab.css b/src/web/traceViewer/ui/snapshotTab.css index fbe10b936d..765cf6ff9f 100644 --- a/src/web/traceViewer/ui/snapshotTab.css +++ b/src/web/traceViewer/ui/snapshotTab.css @@ -17,17 +17,20 @@ .snapshot-tab { display: flex; flex: auto; + flex-direction: column; align-items: stretch; outline: none; - padding-right: 60px; } .snapshot-controls { - flex: 0 0 60px; + flex: none; + color: var(--toolbar-color); display: flex; - flex-direction: column; + box-shadow: var(--box-shadow); + background-color: var(--toolbar-bg-color); + height: 32px; align-items: center; - padding: 5px 0 0 5px; + justify-content: center; } .snapshot-toggle { diff --git a/src/web/traceViewer/ui/snapshotTab.tsx b/src/web/traceViewer/ui/snapshotTab.tsx index 26e92712a1..5584829777 100644 --- a/src/web/traceViewer/ui/snapshotTab.tsx +++ b/src/web/traceViewer/ui/snapshotTab.tsx @@ -17,6 +17,7 @@ import { ActionEntry } from '../../../server/trace/viewer/traceModel'; import { Size } from '../geometry'; import './snapshotTab.css'; +import './tabbedPane.css'; import * as React from 'react'; import { useMeasure } from './helpers'; import type { Point } from '../../../common/types'; @@ -26,9 +27,16 @@ export const SnapshotTab: React.FunctionComponent<{ snapshotSize: Size, }> = ({ actionEntry, snapshotSize }) => { const [measure, ref] = useMeasure(); - const [snapshotIndex, setSnapshotIndex] = React.useState(0); + let [snapshotIndex, setSnapshotIndex] = React.useState(0); - const snapshots = actionEntry ? (actionEntry.snapshots || []) : []; + const snapshotMap = new Map(); + for (const snapshot of actionEntry?.snapshots || []) + snapshotMap.set(snapshot.title, snapshot); + const actionSnapshot = snapshotMap.get('action') || snapshotMap.get('before'); + const snapshots = [actionSnapshot ? { ...actionSnapshot, title: 'action' } : undefined, snapshotMap.get('before'), snapshotMap.get('after')].filter(Boolean) as { title: string, snapshotName: string }[]; + + if (snapshotIndex >= snapshots.length) + snapshotIndex = snapshots.length - 1; const iframeRef = React.createRef(); React.useEffect(() => { @@ -60,21 +68,20 @@ export const SnapshotTab: React.FunctionComponent<{ className='snapshot-tab' tabIndex={0} onKeyDown={event => { - if (event.key === 'ArrowDown') + if (event.key === 'ArrowRight') setSnapshotIndex(Math.min(snapshotIndex + 1, snapshots.length - 1)); - if (event.key === 'ArrowUp') + if (event.key === 'ArrowLeft') setSnapshotIndex(Math.max(snapshotIndex - 1, 0)); }} - >
+ >
{snapshots.map((snapshot, index) => { - return
setSnapshotIndex(index)}> - {snapshot.title} + return
setSnapshotIndex(index)} + key={snapshot.title}> +
{renderTitle(snapshot.title)}
- }) - }
+ })} +
; }; + +function renderTitle(snapshotTitle: string): string { + if (snapshotTitle === 'before') + return 'Before'; + if (snapshotTitle === 'after') + return 'After'; + if (snapshotTitle === 'action') + return 'Action'; + return snapshotTitle; +} diff --git a/src/web/traceViewer/ui/sourceTab.tsx b/src/web/traceViewer/ui/sourceTab.tsx index 8d75454664..12aebe05ea 100644 --- a/src/web/traceViewer/ui/sourceTab.tsx +++ b/src/web/traceViewer/ui/sourceTab.tsx @@ -78,7 +78,7 @@ export const SourceTab: React.FunctionComponent<{ } }, [needReveal, targetLineRef]); - return + return ; diff --git a/src/web/traceViewer/ui/tabbedPane.css b/src/web/traceViewer/ui/tabbedPane.css index 82a4964d71..e38ca83334 100644 --- a/src/web/traceViewer/ui/tabbedPane.css +++ b/src/web/traceViewer/ui/tabbedPane.css @@ -31,7 +31,7 @@ display: flex; box-shadow: var(--box-shadow); background-color: var(--toolbar-bg-color); - height: 40px; + height: 32px; align-items: center; padding-right: 10px; flex: none; diff --git a/src/web/traceViewer/ui/timeline.css b/src/web/traceViewer/ui/timeline.css index f36cd409a7..101231535c 100644 --- a/src/web/traceViewer/ui/timeline.css +++ b/src/web/traceViewer/ui/timeline.css @@ -20,7 +20,6 @@ position: relative; display: flex; flex-direction: column; - border-bottom: 1px solid #ddd; padding: 20px 0 5px; cursor: text; } diff --git a/src/web/traceViewer/ui/timeline.tsx b/src/web/traceViewer/ui/timeline.tsx index c861b9a49e..2b8ebc3ff0 100644 --- a/src/web/traceViewer/ui/timeline.tsx +++ b/src/web/traceViewer/ui/timeline.tsx @@ -41,7 +41,8 @@ export const Timeline: React.FunctionComponent<{ selectedAction: ActionEntry | undefined, highlightedAction: ActionEntry | undefined, onSelected: (action: ActionEntry) => void, -}> = ({ context, boundaries, selectedAction, highlightedAction, onSelected }) => { + onHighlighted: (action: ActionEntry | undefined) => void, +}> = ({ context, boundaries, selectedAction, highlightedAction, onSelected, onHighlighted }) => { const [measure, ref] = useMeasure(); const [previewX, setPreviewX] = React.useState(); const [hoveredBarIndex, setHoveredBarIndex] = React.useState(); @@ -144,13 +145,21 @@ export const Timeline: React.FunctionComponent<{ if (!ref.current) return; const x = event.clientX - ref.current.getBoundingClientRect().left; + const index = findHoveredBarIndex(x); setPreviewX(x); - setHoveredBarIndex(findHoveredBarIndex(x)); + setHoveredBarIndex(index); + if (typeof index === 'number') + onHighlighted(bars[index].entry); }; + const onMouseLeave = () => { setPreviewX(undefined); + setHoveredBarIndex(undefined); + onHighlighted(undefined); }; + const onClick = (event: React.MouseEvent) => { + setPreviewX(undefined); if (!ref.current) return; const x = event.clientX - ref.current.getBoundingClientRect().left; diff --git a/src/web/traceViewer/ui/workbench.tsx b/src/web/traceViewer/ui/workbench.tsx index d15dc51913..ae452aaddb 100644 --- a/src/web/traceViewer/ui/workbench.tsx +++ b/src/web/traceViewer/ui/workbench.tsx @@ -64,17 +64,18 @@ export const Workbench: React.FunctionComponent<{ }} />
-
+
setSelectedAction(action)} + onHighlighted={action => setHighlightedAction(action)} />
- - + + },