feat(ui-mode): highlight console message in timeline on hover (#31756)
This commit is contained in:
parent
097f28def9
commit
6dbc7b54e8
|
|
@ -107,13 +107,17 @@ export const ConsoleTab: React.FunctionComponent<{
|
||||||
boundaries: Boundaries,
|
boundaries: Boundaries,
|
||||||
consoleModel: ConsoleTabModel,
|
consoleModel: ConsoleTabModel,
|
||||||
selectedTime: Boundaries | undefined,
|
selectedTime: Boundaries | undefined,
|
||||||
}> = ({ consoleModel, boundaries }) => {
|
onEntryHovered: (entry: ConsoleEntry | undefined) => void,
|
||||||
|
onAccepted: (entry: ConsoleEntry) => void,
|
||||||
|
}> = ({ consoleModel, boundaries, onEntryHovered, onAccepted }) => {
|
||||||
if (!consoleModel.entries.length)
|
if (!consoleModel.entries.length)
|
||||||
return <PlaceholderPanel text='No console entries' />;
|
return <PlaceholderPanel text='No console entries' />;
|
||||||
|
|
||||||
return <div className='console-tab'>
|
return <div className='console-tab'>
|
||||||
<ConsoleListView
|
<ConsoleListView
|
||||||
name='console'
|
name='console'
|
||||||
|
onAccepted={onAccepted}
|
||||||
|
onHighlighted={onEntryHovered}
|
||||||
items={consoleModel.entries}
|
items={consoleModel.entries}
|
||||||
isError={entry => entry.isError}
|
isError={entry => entry.isError}
|
||||||
isWarning={entry => entry.isWarning}
|
isWarning={entry => entry.isWarning}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,11 @@
|
||||||
--action-background-color: #1a85ff66;
|
--action-background-color: #1a85ff66;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-bar.console-message {
|
||||||
|
--action-color: var(--vscode-charts-purple);
|
||||||
|
--action-background-color: #1a85ff66;
|
||||||
|
}
|
||||||
|
|
||||||
body.dark-mode .timeline-bar.action.error {
|
body.dark-mode .timeline-bar.action.error {
|
||||||
--action-color: var(--vscode-errorForeground);
|
--action-color: var(--vscode-errorForeground);
|
||||||
--action-background-color: #f4877166;
|
--action-background-color: #f4877166;
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,12 @@ import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||||
import './timeline.css';
|
import './timeline.css';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import type { Entry } from '@trace/har';
|
import type { Entry } from '@trace/har';
|
||||||
|
import type { ConsoleEntry } from './consoleTab';
|
||||||
|
|
||||||
type TimelineBar = {
|
type TimelineBar = {
|
||||||
action?: ActionTraceEventInContext;
|
action?: ActionTraceEventInContext;
|
||||||
resource?: Entry;
|
resource?: Entry;
|
||||||
|
consoleMessage?: ConsoleEntry;
|
||||||
leftPosition: number;
|
leftPosition: number;
|
||||||
rightPosition: number;
|
rightPosition: number;
|
||||||
leftTime: number;
|
leftTime: number;
|
||||||
|
|
@ -38,14 +40,16 @@ type TimelineBar = {
|
||||||
|
|
||||||
export const Timeline: React.FunctionComponent<{
|
export const Timeline: React.FunctionComponent<{
|
||||||
model: MultiTraceModel | undefined,
|
model: MultiTraceModel | undefined,
|
||||||
|
consoleEntries: ConsoleEntry[] | undefined,
|
||||||
boundaries: Boundaries,
|
boundaries: Boundaries,
|
||||||
highlightedAction: ActionTraceEventInContext | undefined,
|
highlightedAction: ActionTraceEventInContext | undefined,
|
||||||
highlightedEntry: Entry | undefined,
|
highlightedEntry: Entry | undefined,
|
||||||
|
highlightedConsoleEntry: ConsoleEntry | undefined,
|
||||||
onSelected: (action: ActionTraceEventInContext) => void,
|
onSelected: (action: ActionTraceEventInContext) => void,
|
||||||
selectedTime: Boundaries | undefined,
|
selectedTime: Boundaries | undefined,
|
||||||
setSelectedTime: (time: Boundaries | undefined) => void,
|
setSelectedTime: (time: Boundaries | undefined) => void,
|
||||||
sdkLanguage: Language,
|
sdkLanguage: Language,
|
||||||
}> = ({ model, boundaries, onSelected, highlightedAction, highlightedEntry, selectedTime, setSelectedTime, sdkLanguage }) => {
|
}> = ({ model, boundaries, consoleEntries, onSelected, highlightedAction, highlightedEntry, highlightedConsoleEntry, selectedTime, setSelectedTime, sdkLanguage }) => {
|
||||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||||
const [dragWindow, setDragWindow] = React.useState<{ startX: number, endX: number, pivot?: number, type: 'resize' | 'move' } | undefined>();
|
const [dragWindow, setDragWindow] = React.useState<{ startX: number, endX: number, pivot?: number, type: 'resize' | 'move' } | undefined>();
|
||||||
const [previewPoint, setPreviewPoint] = React.useState<FilmStripPreviewPoint | undefined>();
|
const [previewPoint, setPreviewPoint] = React.useState<FilmStripPreviewPoint | undefined>();
|
||||||
|
|
@ -92,13 +96,34 @@ export const Timeline: React.FunctionComponent<{
|
||||||
error: false,
|
error: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const consoleMessage of consoleEntries || []) {
|
||||||
|
bars.push({
|
||||||
|
consoleMessage,
|
||||||
|
leftTime: consoleMessage.timestamp,
|
||||||
|
rightTime: consoleMessage.timestamp,
|
||||||
|
leftPosition: timeToPosition(measure.width, boundaries, consoleMessage.timestamp),
|
||||||
|
rightPosition: timeToPosition(measure.width, boundaries, consoleMessage.timestamp),
|
||||||
|
active: false,
|
||||||
|
error: consoleMessage.isError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return bars;
|
return bars;
|
||||||
}, [model, boundaries, measure]);
|
}, [model, consoleEntries, boundaries, measure]);
|
||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
for (const bar of bars)
|
for (const bar of bars) {
|
||||||
bar.active = (!!highlightedAction && bar.action === highlightedAction) || (!!highlightedEntry && bar.resource === highlightedEntry);
|
if (highlightedAction)
|
||||||
}, [bars, highlightedAction, highlightedEntry]);
|
bar.active = bar.action === highlightedAction;
|
||||||
|
else if (highlightedEntry)
|
||||||
|
bar.active = bar.resource === highlightedEntry;
|
||||||
|
else if (highlightedConsoleEntry)
|
||||||
|
bar.active = bar.consoleMessage === highlightedConsoleEntry;
|
||||||
|
else
|
||||||
|
bar.active = false;
|
||||||
|
}
|
||||||
|
}, [bars, highlightedAction, highlightedEntry, highlightedConsoleEntry]);
|
||||||
|
|
||||||
const onMouseDown = React.useCallback((event: React.MouseEvent) => {
|
const onMouseDown = React.useCallback((event: React.MouseEvent) => {
|
||||||
setPreviewPoint(undefined);
|
setPreviewPoint(undefined);
|
||||||
|
|
@ -229,11 +254,12 @@ export const Timeline: React.FunctionComponent<{
|
||||||
return <div key={index}
|
return <div key={index}
|
||||||
className={'timeline-bar' + (bar.action ? ' action' : '')
|
className={'timeline-bar' + (bar.action ? ' action' : '')
|
||||||
+ (bar.resource ? ' network' : '')
|
+ (bar.resource ? ' network' : '')
|
||||||
|
+ (bar.consoleMessage ? ' console-message' : '')
|
||||||
+ (bar.active ? ' active' : '')
|
+ (bar.active ? ' active' : '')
|
||||||
+ (bar.error ? ' error' : '')}
|
+ (bar.error ? ' error' : '')}
|
||||||
style={{
|
style={{
|
||||||
left: bar.leftPosition,
|
left: bar.leftPosition,
|
||||||
width: Math.max(1, bar.rightPosition - bar.leftPosition),
|
width: Math.max(5, bar.rightPosition - bar.leftPosition),
|
||||||
top: barTop(bar),
|
top: barTop(bar),
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { ActionList } from './actionList';
|
||||||
import { CallTab } from './callTab';
|
import { CallTab } from './callTab';
|
||||||
import { LogTab } from './logTab';
|
import { LogTab } from './logTab';
|
||||||
import { ErrorsTab, useErrorsTabModel } from './errorsTab';
|
import { ErrorsTab, useErrorsTabModel } from './errorsTab';
|
||||||
|
import type { ConsoleEntry } from './consoleTab';
|
||||||
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
|
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
|
||||||
import type * as modelUtil from './modelUtil';
|
import type * as modelUtil from './modelUtil';
|
||||||
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||||
|
|
@ -57,6 +58,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
|
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
|
||||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
|
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
|
||||||
const [highlightedEntry, setHighlightedEntry] = React.useState<Entry | undefined>();
|
const [highlightedEntry, setHighlightedEntry] = React.useState<Entry | undefined>();
|
||||||
|
const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>();
|
||||||
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
||||||
const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('propertiesTab', showSourcesFirst ? 'source' : 'call');
|
const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('propertiesTab', showSourcesFirst ? 'source' : 'call');
|
||||||
const [isInspecting, setIsInspecting] = React.useState(false);
|
const [isInspecting, setIsInspecting] = React.useState(false);
|
||||||
|
|
@ -167,7 +169,13 @@ export const Workbench: React.FunctionComponent<{
|
||||||
id: 'console',
|
id: 'console',
|
||||||
title: 'Console',
|
title: 'Console',
|
||||||
count: consoleModel.entries.length,
|
count: consoleModel.entries.length,
|
||||||
render: () => <ConsoleTab consoleModel={consoleModel} boundaries={boundaries} selectedTime={selectedTime} />
|
render: () => <ConsoleTab
|
||||||
|
consoleModel={consoleModel}
|
||||||
|
boundaries={boundaries}
|
||||||
|
selectedTime={selectedTime}
|
||||||
|
onAccepted={m => setSelectedTime({ minimum: m.timestamp, maximum: m.timestamp })}
|
||||||
|
onEntryHovered={setHighlightedConsoleMessage}
|
||||||
|
/>
|
||||||
};
|
};
|
||||||
const networkTab: TabbedPaneTabModel = {
|
const networkTab: TabbedPaneTabModel = {
|
||||||
id: 'network',
|
id: 'network',
|
||||||
|
|
@ -218,9 +226,11 @@ export const Workbench: React.FunctionComponent<{
|
||||||
return <div className='vbox workbench' {...(inert ? { inert: 'true' } : {})}>
|
return <div className='vbox workbench' {...(inert ? { inert: 'true' } : {})}>
|
||||||
<Timeline
|
<Timeline
|
||||||
model={model}
|
model={model}
|
||||||
|
consoleEntries={consoleModel.entries}
|
||||||
boundaries={boundaries}
|
boundaries={boundaries}
|
||||||
highlightedAction={highlightedAction}
|
highlightedAction={highlightedAction}
|
||||||
highlightedEntry={highlightedEntry}
|
highlightedEntry={highlightedEntry}
|
||||||
|
highlightedConsoleEntry={highlightedConsoleMessage}
|
||||||
onSelected={onActionSelected}
|
onSelected={onActionSelected}
|
||||||
sdkLanguage={sdkLanguage}
|
sdkLanguage={sdkLanguage}
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ export function ListView<T>({
|
||||||
<div
|
<div
|
||||||
className='list-view-content'
|
className='list-view-content'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onDoubleClick={() => selectedItem && onAccepted?.(selectedItem, items.indexOf(selectedItem))}
|
|
||||||
onKeyDown={event => {
|
onKeyDown={event => {
|
||||||
if (selectedItem && event.key === 'Enter') {
|
if (selectedItem && event.key === 'Enter') {
|
||||||
onAccepted?.(selectedItem, items.indexOf(selectedItem));
|
onAccepted?.(selectedItem, items.indexOf(selectedItem));
|
||||||
|
|
@ -143,6 +142,7 @@ export function ListView<T>({
|
||||||
const rendered = render(item, index);
|
const rendered = render(item, index);
|
||||||
return <div
|
return <div
|
||||||
key={id?.(item, index) || index}
|
key={id?.(item, index) || index}
|
||||||
|
onDoubleClick={() => onAccepted?.(item, index)}
|
||||||
role='listitem'
|
role='listitem'
|
||||||
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix}
|
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix}
|
||||||
onClick={() => onSelected?.(item, index)}
|
onClick={() => onSelected?.(item, index)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue