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