feat(ui-mode): highlight console message in timeline on hover (#31756)

This commit is contained in:
Max Schmitt 2024-07-18 16:39:40 +02:00 committed by GitHub
parent 097f28def9
commit 6dbc7b54e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 54 additions and 9 deletions

View file

@ -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}

View file

@ -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;

View file

@ -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,
}} }}

View file

@ -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}

View file

@ -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)}