mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-15 06:02:57 +03:00
chore(tracing): sync timeline and list highlight (#6235)
This commit is contained in:
parent
27e720f23a
commit
033bc9bfcc
@ -31,6 +31,7 @@
|
||||
--transparent-blue: #2196F355;
|
||||
--orange: #d24726;
|
||||
--black: #1E1E1E;
|
||||
--light-gray: #BBBBBB;
|
||||
--gray: #888888;
|
||||
--separator: #80808059;
|
||||
--focus-ring: #0E639CCC;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<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>;
|
||||
};
|
||||
|
@ -20,6 +20,7 @@
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
padding-top: 3px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
|
@ -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 {
|
||||
|
@ -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<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(() => {
|
||||
@ -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',
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -20,7 +20,6 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 20px 0 5px;
|
||||
cursor: text;
|
||||
}
|
||||
|
@ -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>();
|
||||
@ -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;
|
||||
|
@ -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} /> },
|
||||
|
Loading…
Reference in New Issue
Block a user