Skip to content

Commit

Permalink
chore(tracing): sync timeline and list highlight (#6235)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Apr 20, 2021
1 parent 27e720f commit 033bc9b
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 60 deletions.
1 change: 1 addition & 0 deletions src/web/common.css
Expand Up @@ -31,6 +31,7 @@
--transparent-blue: #2196F355;
--orange: #d24726;
--black: #1E1E1E;
--light-gray: #BBBBBB;
--gray: #888888;
--separator: #80808059;
--focus-ring: #0E639CCC;
Expand Down
16 changes: 14 additions & 2 deletions src/web/traceViewer/ui/actionList.css
Expand Up @@ -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;
Expand All @@ -23,6 +27,7 @@
color: #555;
overflow: auto;
outline: none;
padding: 5px 0 0 5px;
}

.action-entry {
Expand All @@ -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;
}
Expand Down
87 changes: 53 additions & 34 deletions src/web/traceViewer/ui/actionList.tsx
Expand Up @@ -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 {
Expand All @@ -33,39 +34,57 @@ export const ActionList: React.FC<ActionListProps> = ({
onSelected = () => {},
onHighlighted = () => {},
}) => {
return <div
className='action-list'
tabIndex={0}
onKeyDown={event => {
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 <div
className={'action-entry' + (actionEntry === selectedAction ? ' selected' : '')}
key={actionId}
onClick={() => onSelected(actionEntry)}
onMouseEnter={() => onHighlighted(actionEntry)}
onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)}
const actionListRef = React.createRef<HTMLDivElement>();

React.useEffect(() => {
actionListRef.current?.focus();
}, [selectedAction]);

return <div className='action-list vbox'>
<div className='.action-list-title tab-strip'>
<div className='tab-element'>
<div className='tab-label'>Actions</div>
</div>
</div>
<div
className='action-list-content'
tabIndex={0}
onKeyDown={event => {
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}
>
<div className={'action-error codicon codicon-issues'} hidden={!metadata.error} />
<div className='action-title'>{metadata.apiName}</div>
{metadata.params.selector && <div className='action-selector' title={metadata.params.selector}>{metadata.params.selector}</div>}
{metadata.method === 'goto' && metadata.params.url && <div className='action-url' title={metadata.params.url}>{metadata.params.url}</div>}
</div>;
})}</div>;
{actions.map(actionEntry => {
const { metadata, actionId } = actionEntry;
const selectedSuffix = actionEntry === selectedAction ? ' selected' : '';
const highlightedSuffix = actionEntry === highlightedAction ? ' highlighted' : '';
return <div
className={'action-entry' + selectedSuffix + highlightedSuffix}
key={actionId}
onClick={() => onSelected(actionEntry)}
onMouseEnter={() => onHighlighted(actionEntry)}
onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)}
>
<div className={'action-error codicon codicon-issues'} hidden={!metadata.error} />
<div className='action-title'>{metadata.apiName}</div>
{metadata.params.selector && <div className='action-selector' title={metadata.params.selector}>{metadata.params.selector}</div>}
{metadata.method === 'goto' && metadata.params.url && <div className='action-url' title={metadata.params.url}>{metadata.params.url}</div>}
</div>;
})}
</div>
</div>;
};
1 change: 1 addition & 0 deletions src/web/traceViewer/ui/logsTab.css
Expand Up @@ -20,6 +20,7 @@
white-space: pre;
overflow: auto;
padding-top: 3px;
user-select: text;
}

.log-line {
Expand Down
11 changes: 7 additions & 4 deletions src/web/traceViewer/ui/snapshotTab.css
Expand Up @@ -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 {
Expand Down
41 changes: 29 additions & 12 deletions src/web/traceViewer/ui/snapshotTab.tsx
Expand Up @@ -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';
Expand All @@ -26,9 +27,16 @@ export const SnapshotTab: React.FunctionComponent<{
snapshotSize: Size,
}> = ({ actionEntry, snapshotSize }) => {
const [measure, ref] = useMeasure<HTMLDivElement>();
const [snapshotIndex, setSnapshotIndex] = React.useState(0);
let [snapshotIndex, setSnapshotIndex] = React.useState(0);

const snapshots = actionEntry ? (actionEntry.snapshots || []) : [];
const snapshotMap = new Map<string, { title: string, snapshotName: string }>();
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<HTMLIFrameElement>();
React.useEffect(() => {
Expand Down Expand Up @@ -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));
}}
><div className='snapshot-controls'>
><div className='tab-strip'>
{snapshots.map((snapshot, index) => {
return <div
key={snapshot.title}
className={'snapshot-toggle' + (snapshotIndex === index ? ' toggled' : '')}
onClick={() => setSnapshotIndex(index)}>
{snapshot.title}
return <div className={'tab-element ' + (snapshotIndex === index ? ' selected' : '')}
onClick={() => setSnapshotIndex(index)}
key={snapshot.title}>
<div className='tab-label'>{renderTitle(snapshot.title)}</div>
</div>
})
}</div>
})}
</div>
<div ref={ref} className='snapshot-wrapper'>
<div className='snapshot-container' style={{
width: snapshotSize.width + 'px',
Expand All @@ -86,3 +93,13 @@ export const SnapshotTab: React.FunctionComponent<{
</div>
</div>;
};

function renderTitle(snapshotTitle: string): string {
if (snapshotTitle === 'before')
return 'Before';
if (snapshotTitle === 'after')
return 'After';
if (snapshotTitle === 'action')
return 'Action';
return snapshotTitle;
}
2 changes: 1 addition & 1 deletion src/web/traceViewer/ui/sourceTab.tsx
Expand Up @@ -78,7 +78,7 @@ export const SourceTab: React.FunctionComponent<{
}
}, [needReveal, targetLineRef]);

return <SplitView sidebarSize={250} orientation='horizontal'>
return <SplitView sidebarSize={100} orientation='vertical'>
<SourceView text={content} language='javascript' highlight={[{ line: targetLine, type: 'running' }]} revealLine={targetLine}></SourceView>
<StackTraceView actionEntry={actionEntry} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
</SplitView>;
Expand Down
2 changes: 1 addition & 1 deletion src/web/traceViewer/ui/tabbedPane.css
Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion src/web/traceViewer/ui/timeline.css
Expand Up @@ -20,7 +20,6 @@
position: relative;
display: flex;
flex-direction: column;
border-bottom: 1px solid #ddd;
padding: 20px 0 5px;
cursor: text;
}
Expand Down
13 changes: 11 additions & 2 deletions src/web/traceViewer/ui/timeline.tsx
Expand Up @@ -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<HTMLDivElement>();
const [previewX, setPreviewX] = React.useState<number | undefined>();
const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>();
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions src/web/traceViewer/ui/workbench.tsx
Expand Up @@ -64,17 +64,18 @@ export const Workbench: React.FunctionComponent<{
}}
/>
</div>
<div style={{ background: 'white', paddingLeft: '20px', flex: 'none' }}>
<div style={{ background: 'white', paddingLeft: '20px', flex: 'none', borderBottom: '1px solid #ddd' }}>
<Timeline
context={context}
boundaries={boundaries}
selectedAction={selectedAction}
highlightedAction={highlightedAction}
onSelected={action => setSelectedAction(action)}
onHighlighted={action => setHighlightedAction(action)}
/>
</div>
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={250}>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={300} orientation='horizontal'>
<SnapshotTab actionEntry={selectedAction} snapshotSize={snapshotSize} />
<TabbedPane tabs={[
{ id: 'logs', title: 'Log', render: () => <LogsTab actionEntry={selectedAction} /> },
Expand Down

0 comments on commit 033bc9b

Please sign in to comment.