chore: various trace viewer ui fixes (#21447)

This commit is contained in:
Pavel Feldman 2023-03-06 21:37:39 -08:00 committed by GitHub
parent 65117702e7
commit 9e477a183e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 23 deletions

View File

@ -29,7 +29,7 @@ export interface ActionListProps {
sdkLanguage: Language | undefined; sdkLanguage: Language | undefined;
onSelected: (action: ActionTraceEvent) => void, onSelected: (action: ActionTraceEvent) => void,
onHighlighted: (action: ActionTraceEvent | undefined) => void, onHighlighted: (action: ActionTraceEvent | undefined) => void,
setSelectedTab: (tab: string) => void, revealConsole: () => void,
} }
export const ActionList: React.FC<ActionListProps> = ({ export const ActionList: React.FC<ActionListProps> = ({
@ -38,7 +38,7 @@ export const ActionList: React.FC<ActionListProps> = ({
sdkLanguage, sdkLanguage,
onSelected = () => {}, onSelected = () => {},
onHighlighted = () => {}, onHighlighted = () => {},
setSelectedTab = () => {}, revealConsole = () => {},
}) => { }) => {
return <ListView return <ListView
items={actions} items={actions}
@ -47,7 +47,7 @@ export const ActionList: React.FC<ActionListProps> = ({
onHighlighted={(action: ActionTraceEvent) => onHighlighted(action)} onHighlighted={(action: ActionTraceEvent) => onHighlighted(action)}
itemKey={(action: ActionTraceEvent) => action.callId} itemKey={(action: ActionTraceEvent) => action.callId}
itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined} itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined}
itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, setSelectedTab)} itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, revealConsole)}
showNoItemsMessage={true} showNoItemsMessage={true}
></ListView>; ></ListView>;
}; };
@ -55,7 +55,7 @@ export const ActionList: React.FC<ActionListProps> = ({
const renderAction = ( const renderAction = (
action: ActionTraceEvent, action: ActionTraceEvent,
sdkLanguage: Language | undefined, sdkLanguage: Language | undefined,
setSelectedTab: (tab: string) => void revealConsole: () => void
) => { ) => {
const { errors, warnings } = modelUtil.stats(action); const { errors, warnings } = modelUtil.stats(action);
const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined; const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined;
@ -67,7 +67,7 @@ const renderAction = (
{action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>} {action.method === 'goto' && action.params.url && <div className='action-url' title={action.params.url}>{action.params.url}</div>}
</div> </div>
<div className='action-duration' style={{ flex: 'none' }}>{action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'}</div> <div className='action-duration' style={{ flex: 'none' }}>{action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'}</div>
<div className='action-icons' onClick={() => setSelectedTab('console')}> <div className='action-icons' onClick={() => revealConsole()}>
{!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>} {!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>}
{!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>} {!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>}
</div> </div>

View File

@ -25,7 +25,7 @@ import type { MultiTraceModel } from './modelUtil';
const tileSize = { width: 200, height: 45 }; const tileSize = { width: 200, height: 45 };
export const FilmStrip: React.FunctionComponent<{ export const FilmStrip: React.FunctionComponent<{
model: MultiTraceModel, model?: MultiTraceModel,
boundaries: Boundaries, boundaries: Boundaries,
previewPoint?: { x: number, clientY: number }, previewPoint?: { x: number, clientY: number },
}> = ({ model, boundaries, previewPoint }) => { }> = ({ model, boundaries, previewPoint }) => {
@ -37,7 +37,7 @@ export const FilmStrip: React.FunctionComponent<{
pageIndex = ((previewPoint.clientY - bounds.top) / tileSize.height) | 0; pageIndex = ((previewPoint.clientY - bounds.top) / tileSize.height) | 0;
} }
const screencastFrames = model.pages[pageIndex]?.screencastFrames; const screencastFrames = model?.pages?.[pageIndex]?.screencastFrames;
let previewImage = undefined; let previewImage = undefined;
let previewSize = undefined; let previewSize = undefined;
if (previewPoint !== undefined && screencastFrames) { if (previewPoint !== undefined && screencastFrames) {
@ -48,7 +48,7 @@ export const FilmStrip: React.FunctionComponent<{
} }
return <div className='film-strip' ref={ref}>{ return <div className='film-strip' ref={ref}>{
model.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane model?.pages.filter(p => p.screencastFrames.length).map((page, index) => <FilmStripLane
boundaries={boundaries} boundaries={boundaries}
page={page} page={page}
width={measure.width} width={measure.width}

View File

@ -20,8 +20,10 @@ import type { MultiTraceModel } from './modelUtil';
import './callTab.css'; import './callTab.css';
export const MetadataView: React.FunctionComponent<{ export const MetadataView: React.FunctionComponent<{
model: MultiTraceModel, model?: MultiTraceModel,
}> = ({ model }) => { }> = ({ model }) => {
if (!model)
return <></>;
return <div className='vbox'> return <div className='vbox'>
<div className='call-section' style={{ paddingTop: 2 }}>Time</div> <div className='call-section' style={{ paddingTop: 2 }}>Time</div>
{model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>} {model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}

View File

@ -16,13 +16,13 @@
.timeline-view { .timeline-view {
flex: none; flex: none;
flex-basis: 60px;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px 0 5px; padding: 20px 0 5px;
cursor: text; cursor: text;
user-select: none; user-select: none;
border-bottom: 1px solid var(--vscode-panel-border);
} }
.timeline-divider { .timeline-divider {
@ -30,7 +30,7 @@
width: 1px; width: 1px;
top: 0; top: 0;
bottom: 0; bottom: 0;
background-color: rgb(0 0 0 / 10%); background-color: var(--vscode-panel-border);
} }
.timeline-time { .timeline-time {

View File

@ -38,7 +38,7 @@ type TimelineBar = {
}; };
export const Timeline: React.FunctionComponent<{ export const Timeline: React.FunctionComponent<{
model: MultiTraceModel, model: MultiTraceModel | undefined,
selectedAction: ActionTraceEvent | undefined, selectedAction: ActionTraceEvent | undefined,
onSelected: (action: ActionTraceEvent) => void, onSelected: (action: ActionTraceEvent) => void,
}> = ({ model, selectedAction, onSelected }) => { }> = ({ model, selectedAction, onSelected }) => {
@ -49,7 +49,7 @@ export const Timeline: React.FunctionComponent<{
const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>(); const [hoveredBarIndex, setHoveredBarIndex] = React.useState<number | undefined>();
const { boundaries, offsets } = React.useMemo(() => { const { boundaries, offsets } = React.useMemo(() => {
const boundaries = { minimum: model.startTime, maximum: model.endTime }; const boundaries = { minimum: model?.startTime || 0, maximum: model?.endTime || 30000 };
// Leave some nice free space on the right hand side. // Leave some nice free space on the right hand side.
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20; boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
return { boundaries, offsets: calculateDividerOffsets(measure.width, boundaries) }; return { boundaries, offsets: calculateDividerOffsets(measure.width, boundaries) };
@ -57,7 +57,7 @@ export const Timeline: React.FunctionComponent<{
const bars = React.useMemo(() => { const bars = React.useMemo(() => {
const bars: TimelineBar[] = []; const bars: TimelineBar[] = [];
for (const entry of model.actions) { for (const entry of model?.actions || []) {
let detail = trimRight(entry.params.selector || '', 50); let detail = trimRight(entry.params.selector || '', 50);
if (entry.method === 'goto') if (entry.method === 'goto')
detail = trimRight(entry.params.url || '', 50); detail = trimRight(entry.params.url || '', 50);
@ -74,7 +74,7 @@ export const Timeline: React.FunctionComponent<{
}); });
} }
for (const event of model.events) { for (const event of model?.events || []) {
const startTime = event.time; const startTime = event.time;
bars.push({ bars.push({
event, event,

View File

@ -53,13 +53,11 @@ export const Workbench: React.FunctionComponent<{
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> }); tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
return <div className='vbox'> return <div className='vbox'>
<div style={{ paddingLeft: '20px', flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}> <Timeline
<Timeline model={model}
model={model} selectedAction={activeAction}
selectedAction={activeAction} onSelected={action => setSelectedAction(action)}
onSelected={action => setSelectedAction(action)} />
/>
</div>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}> <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={300} orientation='vertical'> <SplitView sidebarSize={300} orientation='vertical'>
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} /> <SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
@ -77,7 +75,7 @@ export const Workbench: React.FunctionComponent<{
onHighlighted={action => { onHighlighted={action => {
setHighlightedAction(action); setHighlightedAction(action);
}} }}
setSelectedTab={setSelectedPropertiesTab} revealConsole={() => setSelectedPropertiesTab('console')}
/> }, /> },
{ id: 'metadata', { id: 'metadata',
title: 'Metadata', title: 'Metadata',

View File

@ -112,3 +112,23 @@ input[type=text], input[type=search] {
border: none; border: none;
outline: none; outline: none;
} }
body.dark-mode ::-webkit-scrollbar {
width: 10px;
}
body.dark-mode ::-webkit-scrollbar-thumb {
background-color: #555;
}
body.dark-mode ::-webkit-scrollbar-track {
background-color: #333;
}
body.dark-mode ::-webkit-scrollbar-thumb:hover {
background-color: #777;
}
body.dark-mode ::-webkit-scrollbar-track:hover {
background-color: #444;
}

View File

@ -45,3 +45,7 @@ export function toggleTheme() {
document.body.classList.add(newTheme); document.body.classList.add(newTheme);
localStorage.setItem('theme', newTheme); localStorage.setItem('theme', newTheme);
} }
export function isDarkTheme() {
return document.body.classList.contains('dark-mode');
}