mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
fix(trace viewer): follow up with recent instrumentation changes (#5488)
- List all actions we are interested in - Fix timeline hover flicker - Extract tabbed pane component - Preview snapshots without clicking on the action
This commit is contained in:
parent
3248c2449c
commit
da135c2abb
@ -64,6 +64,8 @@ export type VideoMetaInfo = {
|
||||
endTime: number;
|
||||
};
|
||||
|
||||
const kInterestingActions = ['click', 'dblclick', 'hover', 'check', 'uncheck', 'tap', 'fill', 'press', 'type', 'selectOption', 'setInputFiles', 'goto', 'setContent', 'goBack', 'goForward', 'reload'];
|
||||
|
||||
export function readTraceFile(events: trace.TraceEvent[], traceModel: TraceModel, filePath: string) {
|
||||
const contextEntries = new Map<string, ContextEntry>();
|
||||
const pageEntries = new Map<string, PageEntry>();
|
||||
@ -108,6 +110,8 @@ export function readTraceFile(events: trace.TraceEvent[], traceModel: TraceModel
|
||||
break;
|
||||
}
|
||||
case 'action': {
|
||||
if (!kInterestingActions.includes(event.method))
|
||||
break;
|
||||
const pageEntry = pageEntries.get(event.pageId!)!;
|
||||
const actionId = event.contextId + '/' + event.pageId + '/' + pageEntry.actions.length;
|
||||
const action: ActionEntry = {
|
||||
|
@ -1,135 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../cli/traceViewer/traceModel';
|
||||
import { Boundaries, Size } from '../geometry';
|
||||
import { NetworkTab } from './networkTab';
|
||||
import { SourceTab } from './sourceTab';
|
||||
import './propertiesTabbedPane.css';
|
||||
import * as React from 'react';
|
||||
import { msToString, useMeasure } from './helpers';
|
||||
import { LogsTab } from './logsTab';
|
||||
|
||||
export const PropertiesTabbedPane: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
snapshotSize: Size,
|
||||
selectedTime: number | undefined,
|
||||
boundaries: Boundaries,
|
||||
}> = ({ actionEntry, snapshotSize, selectedTime, boundaries }) => {
|
||||
const [selected, setSelected] = React.useState<'snapshot' | 'source' | 'network' | 'logs'>('snapshot');
|
||||
return <div className='properties-tabbed-pane'>
|
||||
<div className='vbox'>
|
||||
<div className='hbox' style={{ flex: 'none' }}>
|
||||
<div className='properties-tab-strip'>
|
||||
<div className={'properties-tab-element ' + (selected === 'snapshot' ? 'selected' : '')}
|
||||
onClick={() => setSelected('snapshot')}>
|
||||
<div className='properties-tab-label'>Snapshot</div>
|
||||
</div>
|
||||
<div className={'properties-tab-element ' + (selected === 'source' ? 'selected' : '')}
|
||||
onClick={() => setSelected('source')}>
|
||||
<div className='properties-tab-label'>Source</div>
|
||||
</div>
|
||||
<div className={'properties-tab-element ' + (selected === 'network' ? 'selected' : '')}
|
||||
onClick={() => setSelected('network')}>
|
||||
<div className='properties-tab-label'>Network</div>
|
||||
</div>
|
||||
<div className={'properties-tab-element ' + (selected === 'logs' ? 'selected' : '')}
|
||||
onClick={() => setSelected('logs')}>
|
||||
<div className='properties-tab-label'>Logs</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ selected === 'snapshot' && <div className='properties-tab-content'>
|
||||
<SnapshotTab actionEntry={actionEntry} snapshotSize={snapshotSize} selectedTime={selectedTime} boundaries={boundaries} />
|
||||
</div> }
|
||||
{ selected === 'source' && <div className='properties-tab-content'>
|
||||
<SourceTab actionEntry={actionEntry} />
|
||||
</div> }
|
||||
{ selected === 'network' && <div className='properties-tab-content'>
|
||||
<NetworkTab actionEntry={actionEntry} />
|
||||
</div> }
|
||||
{ selected === 'logs' && <div className='properties-tab-content'>
|
||||
<LogsTab actionEntry={actionEntry} />
|
||||
</div> }
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const SnapshotTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
snapshotSize: Size,
|
||||
selectedTime: number | undefined,
|
||||
boundaries: Boundaries,
|
||||
}> = ({ actionEntry, snapshotSize, selectedTime, boundaries }) => {
|
||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||
const [snapshotIndex, setSnapshotIndex] = React.useState(0);
|
||||
|
||||
let snapshots: { name: string, snapshotId?: string, snapshotTime?: number }[] = [];
|
||||
snapshots = (actionEntry ? (actionEntry.action.snapshots || []) : []).slice();
|
||||
if (!snapshots.length || snapshots[0].name !== 'before')
|
||||
snapshots.unshift({ name: 'before', snapshotTime: actionEntry ? actionEntry.action.startTime : 0 });
|
||||
if (snapshots[snapshots.length - 1].name !== 'after')
|
||||
snapshots.push({ name: 'after', snapshotTime: actionEntry ? actionEntry.action.endTime : 0 });
|
||||
|
||||
const iframeRef = React.createRef<HTMLIFrameElement>();
|
||||
React.useEffect(() => {
|
||||
if (!actionEntry || !iframeRef.current)
|
||||
return;
|
||||
|
||||
// TODO: this logic is copied from SnapshotServer. Find a way to share.
|
||||
let snapshotUrl = 'data:text/html,Snapshot is not available';
|
||||
if (selectedTime) {
|
||||
snapshotUrl = `/snapshot/pageId/${actionEntry.action.pageId!}/timestamp/${selectedTime}/main`;
|
||||
} else {
|
||||
const snapshot = snapshots[snapshotIndex];
|
||||
if (snapshot && snapshot.snapshotTime)
|
||||
snapshotUrl = `/snapshot/pageId/${actionEntry.action.pageId!}/timestamp/${snapshot.snapshotTime}/main`;
|
||||
else if (snapshot && snapshot.snapshotId)
|
||||
snapshotUrl = `/snapshot/pageId/${actionEntry.action.pageId!}/snapshotId/${snapshot.snapshotId}/main`;
|
||||
}
|
||||
|
||||
try {
|
||||
(iframeRef.current.contentWindow as any).showSnapshot(snapshotUrl);
|
||||
} catch (e) {
|
||||
}
|
||||
}, [actionEntry, snapshotIndex, selectedTime]);
|
||||
|
||||
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
|
||||
return <div className='snapshot-tab'>
|
||||
<div className='snapshot-controls'>{
|
||||
selectedTime && <div key='selectedTime' className='snapshot-toggle'>
|
||||
{msToString(selectedTime - boundaries.minimum)}
|
||||
</div>
|
||||
}{!selectedTime && snapshots.map((snapshot, index) => {
|
||||
return <div
|
||||
key={snapshot.name}
|
||||
className={'snapshot-toggle' + (snapshotIndex === index ? ' toggled' : '')}
|
||||
onClick={() => setSnapshotIndex(index)}>
|
||||
{snapshot.name}
|
||||
</div>
|
||||
})
|
||||
}</div>
|
||||
<div ref={ref} className='snapshot-wrapper'>
|
||||
<div className='snapshot-container' style={{
|
||||
width: snapshotSize.width + 'px',
|
||||
height: snapshotSize.height + 'px',
|
||||
transform: `translate(${-snapshotSize.width * (1 - scale) / 2}px, ${-snapshotSize.height * (1 - scale) / 2}px) scale(${scale})`,
|
||||
}}>
|
||||
<iframe ref={iframeRef} id='snapshot' name='snapshot' src='/snapshot/'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
54
src/web/traceViewer/ui/snapshotTab.css
Normal file
54
src/web/traceViewer/ui/snapshotTab.css
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.snapshot-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.snapshot-controls {
|
||||
flex: 0 0 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.snapshot-toggle {
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.snapshot-toggle.toggled {
|
||||
background: var(--inactive-focus-ring);
|
||||
}
|
||||
|
||||
.snapshot-wrapper {
|
||||
flex: auto;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.snapshot-container {
|
||||
display: block;
|
||||
background: white;
|
||||
outline: 1px solid #aaa;
|
||||
}
|
||||
|
||||
iframe#snapshot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
90
src/web/traceViewer/ui/snapshotTab.tsx
Normal file
90
src/web/traceViewer/ui/snapshotTab.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../cli/traceViewer/traceModel';
|
||||
import { Boundaries, Size } from '../geometry';
|
||||
import './snapshotTab.css';
|
||||
import * as React from 'react';
|
||||
import { msToString, useMeasure } from './helpers';
|
||||
|
||||
export const SnapshotTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
snapshotSize: Size,
|
||||
selection: { pageId: string, time: number } | undefined,
|
||||
boundaries: Boundaries,
|
||||
}> = ({ actionEntry, snapshotSize, selection, boundaries }) => {
|
||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||
const [snapshotIndex, setSnapshotIndex] = React.useState(0);
|
||||
|
||||
let snapshots: { name: string, snapshotId?: string, snapshotTime?: number }[] = [];
|
||||
snapshots = (actionEntry ? (actionEntry.action.snapshots || []) : []).slice();
|
||||
if (actionEntry) {
|
||||
if (!snapshots.length || snapshots[0].name !== 'before')
|
||||
snapshots.unshift({ name: 'before', snapshotTime: actionEntry ? actionEntry.action.startTime : 0 });
|
||||
if (snapshots[snapshots.length - 1].name !== 'after')
|
||||
snapshots.push({ name: 'after', snapshotTime: actionEntry ? actionEntry.action.endTime : 0 });
|
||||
}
|
||||
const { pageId, time } = selection || { pageId: undefined, time: 0 };
|
||||
|
||||
const iframeRef = React.createRef<HTMLIFrameElement>();
|
||||
React.useEffect(() => {
|
||||
if (!iframeRef.current)
|
||||
return;
|
||||
|
||||
// TODO: this logic is copied from SnapshotServer. Find a way to share.
|
||||
let snapshotUrl = 'data:text/html,Snapshot is not available';
|
||||
if (pageId) {
|
||||
snapshotUrl = `/snapshot/pageId/${pageId}/timestamp/${time}/main`;
|
||||
} else if (actionEntry) {
|
||||
const snapshot = snapshots[snapshotIndex];
|
||||
if (snapshot && snapshot.snapshotTime)
|
||||
snapshotUrl = `/snapshot/pageId/${actionEntry.action.pageId!}/timestamp/${snapshot.snapshotTime}/main`;
|
||||
else if (snapshot && snapshot.snapshotId)
|
||||
snapshotUrl = `/snapshot/pageId/${actionEntry.action.pageId!}/snapshotId/${snapshot.snapshotId}/main`;
|
||||
}
|
||||
|
||||
try {
|
||||
(iframeRef.current.contentWindow as any).showSnapshot(snapshotUrl);
|
||||
} catch (e) {
|
||||
}
|
||||
}, [actionEntry, snapshotIndex, pageId, time]);
|
||||
|
||||
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
|
||||
return <div className='snapshot-tab'>
|
||||
<div className='snapshot-controls'>{
|
||||
selection && <div key='selectedTime' className='snapshot-toggle'>
|
||||
{msToString(selection.time - boundaries.minimum)}
|
||||
</div>
|
||||
}{!selection && snapshots.map((snapshot, index) => {
|
||||
return <div
|
||||
key={snapshot.name}
|
||||
className={'snapshot-toggle' + (snapshotIndex === index ? ' toggled' : '')}
|
||||
onClick={() => setSnapshotIndex(index)}>
|
||||
{snapshot.name}
|
||||
</div>
|
||||
})
|
||||
}</div>
|
||||
<div ref={ref} className='snapshot-wrapper'>
|
||||
<div className='snapshot-container' style={{
|
||||
width: snapshotSize.width + 'px',
|
||||
height: snapshotSize.height + 'px',
|
||||
transform: `translate(${-snapshotSize.width * (1 - scale) / 2}px, ${-snapshotSize.height * (1 - scale) / 2}px) scale(${scale})`,
|
||||
}}>
|
||||
<iframe ref={iframeRef} id='snapshot' name='snapshot' src='/snapshot/'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
@ -14,19 +14,19 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.properties-tabbed-pane {
|
||||
.tabbed-pane {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.properties-tab-content {
|
||||
.tab-content {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.properties-tab-strip {
|
||||
.tab-strip {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -34,11 +34,11 @@
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.properties-tab-strip:focus {
|
||||
.tab-strip:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.properties-tab-element {
|
||||
.tab-element {
|
||||
padding: 2px 6px 0 6px;
|
||||
margin-right: 4px;
|
||||
cursor: pointer;
|
||||
@ -52,7 +52,7 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.properties-tab-label {
|
||||
.tab-label {
|
||||
max-width: 250px;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
@ -60,49 +60,10 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.properties-tab-element.selected {
|
||||
.tab-element.selected {
|
||||
border-bottom-color: var(--color);
|
||||
}
|
||||
|
||||
.properties-tab-element:hover {
|
||||
.tab-element:hover {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.snapshot-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.snapshot-controls {
|
||||
flex: 0 0 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.snapshot-toggle {
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.snapshot-toggle.toggled {
|
||||
background: var(--inactive-focus-ring);
|
||||
}
|
||||
|
||||
.snapshot-wrapper {
|
||||
flex: auto;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.snapshot-container {
|
||||
display: block;
|
||||
background: white;
|
||||
outline: 1px solid #aaa;
|
||||
}
|
||||
|
||||
iframe#snapshot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
51
src/web/traceViewer/ui/tabbedPane.tsx
Normal file
51
src/web/traceViewer/ui/tabbedPane.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './tabbedPane.css';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface TabbedPaneTab {
|
||||
id: string;
|
||||
title: string;
|
||||
render: () => React.ReactElement;
|
||||
}
|
||||
|
||||
export const TabbedPane: React.FunctionComponent<{
|
||||
tabs: TabbedPaneTab[],
|
||||
}> = ({ tabs }) => {
|
||||
const [selected, setSelected] = React.useState<string>(tabs.length ? tabs[0].id : '');
|
||||
return <div className='tabbed-pane'>
|
||||
<div className='vbox'>
|
||||
<div className='hbox' style={{ flex: 'none' }}>
|
||||
<div className='tab-strip'>{
|
||||
tabs.map(tab => {
|
||||
return <div className={'tab-element ' + (selected === tab.id ? 'selected' : '')}
|
||||
onClick={() => setSelected(tab.id)}
|
||||
key={tab.id}>
|
||||
<div className='tab-label'>{tab.title}</div>
|
||||
</div>
|
||||
})
|
||||
}</div>
|
||||
</div>
|
||||
{
|
||||
tabs.map(tab => {
|
||||
if (selected === tab.id)
|
||||
return <div key={tab.id} className='tab-content'>{tab.render()}</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
@ -43,13 +43,12 @@ export const Timeline: React.FunctionComponent<{
|
||||
}> = ({ context, boundaries, selectedAction, highlightedAction, onSelected, onTimeSelected }) => {
|
||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||
const [previewX, setPreviewX] = React.useState<number | undefined>();
|
||||
const [hoveredBar, setHoveredBar] = React.useState<TimelineBar | undefined>();
|
||||
const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>();
|
||||
|
||||
const offsets = React.useMemo(() => {
|
||||
return calculateDividerOffsets(measure.width, boundaries);
|
||||
}, [measure.width, boundaries]);
|
||||
|
||||
let targetBar: TimelineBar | undefined = hoveredBar;
|
||||
const bars = React.useMemo(() => {
|
||||
const bars: TimelineBar[] = [];
|
||||
for (const page of context.pages) {
|
||||
@ -67,8 +66,6 @@ export const Timeline: React.FunctionComponent<{
|
||||
type: entry.action.method,
|
||||
priority: 0,
|
||||
});
|
||||
if (entry === (highlightedAction || selectedAction))
|
||||
targetBar = bars[bars.length - 1];
|
||||
}
|
||||
let lastDialogOpened: trace.DialogOpenedEvent | undefined;
|
||||
for (const event of page.interestingEvents) {
|
||||
@ -116,56 +113,55 @@ export const Timeline: React.FunctionComponent<{
|
||||
return bars;
|
||||
}, [context, boundaries, measure.width]);
|
||||
|
||||
const findHoveredBar = (x: number) => {
|
||||
const hoveredBar = hoveredBarIndex !== undefined ? bars[hoveredBarIndex] : undefined;
|
||||
let targetBar: TimelineBar | undefined = bars.find(bar => bar.entry === (highlightedAction || selectedAction));
|
||||
targetBar = hoveredBar || targetBar;
|
||||
|
||||
const findHoveredBarIndex = (x: number) => {
|
||||
const time = positionToTime(measure.width, boundaries, x);
|
||||
const time1 = positionToTime(measure.width, boundaries, x - 5);
|
||||
const time2 = positionToTime(measure.width, boundaries, x + 5);
|
||||
let bar: TimelineBar | undefined;
|
||||
let index: number | undefined;
|
||||
let distance: number | undefined;
|
||||
for (const b of bars) {
|
||||
const left = Math.max(b.leftTime, time1);
|
||||
const right = Math.min(b.rightTime, time2);
|
||||
const middle = (b.leftTime + b.rightTime) / 2;
|
||||
for (let i = 0; i < bars.length; i++) {
|
||||
const bar = bars[i];
|
||||
const left = Math.max(bar.leftTime, time1);
|
||||
const right = Math.min(bar.rightTime, time2);
|
||||
const middle = (bar.leftTime + bar.rightTime) / 2;
|
||||
const d = Math.abs(time - middle);
|
||||
if (left <= right && (!bar || d < distance!)) {
|
||||
bar = b;
|
||||
if (left <= right && (index === undefined || d < distance!)) {
|
||||
index = i;
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
return bar;
|
||||
return index;
|
||||
};
|
||||
|
||||
const onMouseMove = (event: React.MouseEvent) => {
|
||||
if (ref.current) {
|
||||
const x = event.clientX - ref.current.getBoundingClientRect().left;
|
||||
setPreviewX(x);
|
||||
onTimeSelected(positionToTime(measure.width, boundaries, x));
|
||||
setHoveredBar(findHoveredBar(x));
|
||||
}
|
||||
if (!ref.current)
|
||||
return;
|
||||
const x = event.clientX - ref.current.getBoundingClientRect().left;
|
||||
setPreviewX(x);
|
||||
onTimeSelected(positionToTime(measure.width, boundaries, x));
|
||||
setHoveredBarIndex(findHoveredBarIndex(x));
|
||||
};
|
||||
const onMouseLeave = () => {
|
||||
setPreviewX(undefined);
|
||||
onTimeSelected(undefined);
|
||||
};
|
||||
const onActionClick = (event: React.MouseEvent) => {
|
||||
if (ref.current) {
|
||||
const x = event.clientX - ref.current.getBoundingClientRect().left;
|
||||
const bar = findHoveredBar(x);
|
||||
if (bar && bar.entry)
|
||||
onSelected(bar.entry);
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
const onTimeClick = (event: React.MouseEvent) => {
|
||||
if (ref.current) {
|
||||
const x = event.clientX - ref.current.getBoundingClientRect().left;
|
||||
const time = positionToTime(measure.width, boundaries, x);
|
||||
onTimeSelected(time);
|
||||
event.stopPropagation();
|
||||
}
|
||||
const onClick = (event: React.MouseEvent) => {
|
||||
if (!ref.current)
|
||||
return;
|
||||
const x = event.clientX - ref.current.getBoundingClientRect().left;
|
||||
const index = findHoveredBarIndex(x);
|
||||
if (index === undefined)
|
||||
return;
|
||||
const entry = bars[index].entry;
|
||||
if (entry)
|
||||
onSelected(entry);
|
||||
};
|
||||
|
||||
return <div ref={ref} className='timeline-view' onMouseMove={onMouseMove} onMouseOver={onMouseMove} onMouseLeave={onMouseLeave} onClick={onTimeClick}>
|
||||
return <div ref={ref} className='timeline-view' onMouseMove={onMouseMove} onMouseOver={onMouseMove} onMouseLeave={onMouseLeave} onClick={onClick}>
|
||||
<div className='timeline-grid'>{
|
||||
offsets.map((offset, index) => {
|
||||
return <div key={index} className='timeline-divider' style={{ left: offset.position + 'px' }}>
|
||||
@ -186,7 +182,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
</div>;
|
||||
})
|
||||
}</div>
|
||||
<div className='timeline-lane timeline-bars' onClick={onActionClick}>{
|
||||
<div className='timeline-lane timeline-bars'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-bar ' + bar.type + (targetBar === bar ? ' selected' : '')}
|
||||
|
@ -16,11 +16,15 @@
|
||||
|
||||
import { ActionEntry, TraceModel } from '../../../cli/traceViewer/traceModel';
|
||||
import { ActionList } from './actionList';
|
||||
import { PropertiesTabbedPane } from './propertiesTabbedPane';
|
||||
import { TabbedPane } from './tabbedPane';
|
||||
import { Timeline } from './timeline';
|
||||
import './workbench.css';
|
||||
import * as React from 'react';
|
||||
import { ContextSelector } from './contextSelector';
|
||||
import { NetworkTab } from './networkTab';
|
||||
import { SourceTab } from './sourceTab';
|
||||
import { SnapshotTab } from './snapshotTab';
|
||||
import { LogsTab } from './logsTab';
|
||||
|
||||
export const Workbench: React.FunctionComponent<{
|
||||
traceModel: TraceModel,
|
||||
@ -39,6 +43,7 @@ export const Workbench: React.FunctionComponent<{
|
||||
|
||||
const snapshotSize = context.created.viewportSize || { width: 1280, height: 720 };
|
||||
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
||||
const snapshotSelection = context.pages.length && selectedTime !== undefined ? { pageId: context.pages[0].created.pageId, time: selectedTime } : undefined;
|
||||
|
||||
return <div className='vbox workbench'>
|
||||
<div className='hbox header'>
|
||||
@ -78,12 +83,12 @@ export const Workbench: React.FunctionComponent<{
|
||||
onHighlighted={action => setHighlightedAction(action)}
|
||||
/>
|
||||
</div>
|
||||
<PropertiesTabbedPane
|
||||
actionEntry={selectedAction}
|
||||
snapshotSize={snapshotSize}
|
||||
selectedTime={selectedTime}
|
||||
boundaries={boundaries}
|
||||
/>
|
||||
<TabbedPane tabs={[
|
||||
{ id: 'snapshot', title: 'Snapshot', render: () => <SnapshotTab actionEntry={selectedAction} snapshotSize={snapshotSize} selection={snapshotSelection} boundaries={boundaries} /> },
|
||||
{ id: 'source', title: 'Source', render: () => <SourceTab actionEntry={selectedAction} /> },
|
||||
{ id: 'network', title: 'Network', render: () => <NetworkTab actionEntry={selectedAction} /> },
|
||||
{ id: 'logs', title: 'Logs', render: () => <LogsTab actionEntry={selectedAction} /> },
|
||||
]}/>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user