chore: rearrange settings (#21467)

This commit is contained in:
Pavel Feldman 2023-03-07 12:43:16 -08:00 committed by GitHub
parent c9eac69f2b
commit 9e7abb2a76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 250 additions and 230 deletions

View File

@ -48,7 +48,7 @@ export const ActionList: React.FC<ActionListProps> = ({
itemKey={(action: ActionTraceEvent) => action.callId}
itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined}
itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, revealConsole)}
showNoItemsMessage={true}
noItemsMessage='No actions'
></ListView>;
};

View File

@ -26,7 +26,7 @@ export const MetadataView: React.FunctionComponent<{
return <></>;
return <div className='vbox'>
<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>}
<div className='call-line'>duration:<span className='call-value number' title={msToString(model.endTime - model.startTime)}>{msToString(model.endTime - model.startTime)}</span></div>
<div className='call-section'>Browser</div>
<div className='call-line'>engine:<span className='call-value string' title={model.browserName}>{model.browserName}</span></div>

View File

@ -22,7 +22,7 @@
padding: 20px 0 5px;
cursor: text;
user-select: none;
border-bottom: 1px solid var(--vscode-panel-border);
margin-left: 10px;
}
.timeline-divider {

View File

@ -153,45 +153,47 @@ export const Timeline: React.FunctionComponent<{
onSelected(entry);
};
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' }}>
<div className='timeline-time'>{msToString(offset.time - boundaries.minimum)}</div>
</div>;
})
}</div>
<div className='timeline-lane timeline-labels'>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition,
maxWidth: 100,
}}
>
{bar.label}
</div>;
})
}</div>
<div className='timeline-lane timeline-bars' ref={barsRef}>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition + 'px',
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
top: barTop(bar) + 'px',
}}
title={bar.title}
></div>;
})
}</div>
<FilmStrip model={model} boundaries={boundaries} previewPoint={previewPoint} />
<div className='timeline-marker timeline-marker-hover' style={{
display: (previewPoint !== undefined) ? 'block' : 'none',
left: (previewPoint?.x || 0) + 'px',
}}></div>
return <div style={{ flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
<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' }}>
<div className='timeline-time'>{msToString(offset.time - boundaries.minimum)}</div>
</div>;
})
}</div>
<div className='timeline-lane timeline-labels'>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition,
maxWidth: 100,
}}
>
{bar.label}
</div>;
})
}</div>
<div className='timeline-lane timeline-bars' ref={barsRef}>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition + 'px',
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
top: barTop(bar) + 'px',
}}
title={bar.title}
></div>;
})
}</div>
<FilmStrip model={model} boundaries={boundaries} previewPoint={previewPoint} />
<div className='timeline-marker timeline-marker-hover' style={{
display: (previewPoint !== undefined) ? 'block' : 'none',
left: (previewPoint?.x || 0) + 'px',
}}></div>
</div>
</div>;
};

View File

@ -18,7 +18,7 @@
background-color: var(--vscode-sideBar-background);
}
.watch-mode-sidebar input {
.watch-mode-sidebar input[type=search] {
flex: auto;
}
@ -44,16 +44,11 @@
margin: 0;
}
.watch-mode-sidebar .toolbar h3.title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.watch-mode-sidebar .section-title {
font-size: 11px;
min-width: 3ch;
margin: 0 10px;
-webkit-margin-before: 0;
-webkit-margin-after: 0;
text-transform: uppercase;
font-weight: bold;
margin: 5px;
}
.watch-mode-sidebar .spacer {

View File

@ -33,23 +33,97 @@ import type * as trace from '@trace/trace';
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
let updateStepsProgress: () => void = () => {};
let runWatchedTests = () => {};
let runVisibleTests = () => {};
export const WatchModeView: React.FC<{}> = ({
}) => {
const [projectNames, setProjectNames] = React.useState<string[]>([]);
const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined });
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
const [progress, setProgress] = React.useState<Progress>({ total: 0, passed: 0, failed: 0 });
const [selectedTestItem, setSelectedTestItem] = React.useState<TestItem | undefined>(undefined);
const [settingsVisible, setSettingsVisible] = React.useState<boolean>(false);
updateRootSuite = (rootSuite: Suite, { passed, failed }: Progress) => {
setRootSuite({ value: rootSuite });
progress.passed = passed;
progress.failed = failed;
setProgress({ ...progress });
};
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
const [isRunningTest, setIsRunningTest] = React.useState<boolean>(false);
const [filterText, setFilterText] = React.useState<string>('');
const [projectNames, setProjectNames] = React.useState<string[]>([]);
const [expandedItems, setExpandedItems] = React.useState<Map<string, boolean>>(new Map());
const runTests = (testIds: string[]) => {
setProgress({ total: testIds.length, passed: 0, failed: 0 });
setIsRunningTest(true);
sendMessage('run', { testIds }).then(() => {
setIsRunningTest(false);
});
};
React.useEffect(() => {
if (projectNames.length === 0 && rootSuite.value?.suites.length)
setProjectNames([rootSuite.value?.suites[0].title]);
}, [projectNames, rootSuite]);
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<TraceView testItem={selectedTestItem}></TraceView>
<div className='vbox watch-mode-sidebar'>
<Toolbar>
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
<ToolbarButton icon='play' title='Run' onClick={runVisibleTests} disabled={isRunningTest}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
<div className='spacer'></div>
<ToolbarButton icon='gear' title='Toggle color mode' toggled={settingsVisible} onClick={() => { setSettingsVisible(!settingsVisible); }}></ToolbarButton>
</Toolbar>
{ !settingsVisible && <TestList
projectNames={projectNames}
rootSuite={rootSuite}
isRunningTest={isRunningTest}
runTests={runTests}
onTestItemSelected={setSelectedTestItem} />}
{ settingsVisible && <div className='vbox'>
<div className='hbox' style={{ flex: 'none' }}>
<div className='section-title' style={{ marginTop: 10 }}>Projects</div>
<div className='spacer'></div>
<ToolbarButton icon='close' title='Close settings' toggled={false} onClick={() => setSettingsVisible(false)}></ToolbarButton>
</div>
{(rootSuite.value?.suites || []).map(suite => {
return <div style={{ display: 'flex', alignItems: 'center', lineHeight: '24px' }}>
<input id={`project-${suite.title}`} type='checkbox' checked={projectNames.includes(suite.title)} onClick={() => {
const copy = [...projectNames];
if (copy.includes(suite.title))
copy.splice(copy.indexOf(suite.title), 1);
else
copy.push(suite.title);
setProjectNames(copy);
}} style={{ margin: '0 5px 0 10px' }} />
<label htmlFor={`project-${suite.title}`}>
{suite.title}
</label>
</div>;
})}
<div className='section-title'>Appearance</div>
<div style={{ marginLeft: 3 }}>
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}>Toggle color mode</ToolbarButton>
</div>
</div>}
{isRunningTest && <div className='status-line'>
Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed
</div>}
</div>
</SplitView>;
};
export const TestList: React.FC<{
projectNames: string[],
rootSuite: { value: Suite | undefined },
runTests: (testIds: string[]) => void,
isRunningTest: boolean,
onTestItemSelected: (test: TestItem | undefined) => void,
}> = ({ projectNames, rootSuite, runTests, isRunningTest, onTestItemSelected }) => {
const [filterText, setFilterText] = React.useState<string>('');
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
const [expandedItems, setExpandedItems] = React.useState<Map<string, boolean>>(new Map());
const inputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
@ -57,11 +131,6 @@ export const WatchModeView: React.FC<{}> = ({
resetCollectingRootSuite();
}, []);
React.useEffect(() => {
if (projectNames.length === 0 && rootSuite.value?.suites.length)
setProjectNames([rootSuite.value?.suites[0].title]);
}, [projectNames, rootSuite]);
const { filteredItems, treeItemMap, visibleTestIds } = React.useMemo(() => {
const treeItems = createTree(rootSuite.value, projectNames);
const filteredItems = filterTree(treeItems, filterText);
@ -78,6 +147,7 @@ export const WatchModeView: React.FC<{}> = ({
return { treeItemMap, visibleTestIds, filteredItems };
}, [filterText, rootSuite, projectNames]);
runVisibleTests = () => runTests([...visibleTestIds]);
const { listItems } = React.useMemo(() => {
const listItems = flattenTree(filteredItems, expandedItems, !!filterText.trim());
@ -95,6 +165,8 @@ export const WatchModeView: React.FC<{}> = ({
return { selectedTreeItem, selectedTestItem };
}, [selectedTreeItemId, treeItemMap]);
onTestItemSelected(selectedTestItem);
const runTreeItem = (treeItem: TreeItem) => {
expandedItems.set(treeItem.id, true);
setSelectedTreeItemId(treeItem.id);
@ -105,128 +177,79 @@ export const WatchModeView: React.FC<{}> = ({
runTests(collectTestIds(selectedTreeItem));
};
const runTests = (testIds: string[]) => {
setProgress({ total: testIds.length, passed: 0, failed: 0 });
setIsRunningTest(true);
sendMessage('run', { testIds }).then(() => {
setIsRunningTest(false);
});
};
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<TraceView testItem={selectedTestItem} isRunningTest={isRunningTest}></TraceView>
<div className='vbox watch-mode-sidebar'>
<Toolbar>
<h3 className='title'>Test explorer</h3>
<ToolbarButton icon='play' title='Run' onClick={() => runTests([...visibleTestIds])} disabled={isRunningTest}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
<div className='spacer'></div>
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
</Toolbar>
<Toolbar>
<input ref={inputRef} type='search' placeholder='Filter (e.g. text, @tag)' spellCheck={false} value={filterText}
onChange={e => {
setFilterText(e.target.value);
}}
onKeyDown={e => {
if (e.key === 'Enter')
runTests([...visibleTestIds]);
}}></input>
</Toolbar>
<ListView
items={listItems}
itemKey={(treeItem: TreeItem) => treeItem.id }
itemRender={(treeItem: TreeItem) => {
return <div className='hbox watch-mode-list-item'>
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={isRunningTest}></ToolbarButton>
</div>;
return <div className='vbox'>
<Toolbar>
<input ref={inputRef} type='search' placeholder='Filter (e.g. text, @tag)' spellCheck={false} value={filterText}
onChange={e => {
setFilterText(e.target.value);
}}
itemIcon={(treeItem: TreeItem) => {
if (treeItem.kind === 'case' && treeItem.children?.length === 1)
treeItem = treeItem.children[0];
if (treeItem.kind === 'test') {
const ok = treeItem.test.outcome() === 'expected';
const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected';
const running = treeItem.test.results.some(r => r.duration === -1);
if (running)
return 'codicon-loading';
if (ok)
return 'codicon-check';
if (failed)
return 'codicon-error';
} else {
return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
}
}}
itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2}
selectedItem={selectedTreeItem}
onAccepted={runTreeItem}
onLeftArrow={(treeItem: TreeItem) => {
if (treeItem.children && treeItem.expanded) {
expandedItems.set(treeItem.id, false);
setExpandedItems(new Map(expandedItems));
} else {
setSelectedTreeItemId(treeItem.parent?.id);
}
}}
onRightArrow={(treeItem: TreeItem) => {
if (treeItem.children) {
expandedItems.set(treeItem.id, true);
setExpandedItems(new Map(expandedItems));
}
setRootSuite({ ...rootSuite });
}}
onSelected={(treeItem: TreeItem) => {
setSelectedTreeItemId(treeItem.id);
}}
onIconClicked={(treeItem: TreeItem) => {
if (treeItem.kind === 'test')
return;
if (treeItem.expanded)
expandedItems.set(treeItem.id, false);
else
expandedItems.set(treeItem.id, true);
onKeyDown={e => {
if (e.key === 'Enter')
runVisibleTests();
}}></input>
</Toolbar>
<ListView
items={listItems}
itemKey={(treeItem: TreeItem) => treeItem.id }
itemRender={(treeItem: TreeItem) => {
return <div className='hbox watch-mode-list-item'>
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={isRunningTest}></ToolbarButton>
</div>;
}}
itemIcon={(treeItem: TreeItem) => {
if (treeItem.kind === 'case' && treeItem.children?.length === 1)
treeItem = treeItem.children[0];
if (treeItem.kind === 'test') {
const ok = treeItem.test.outcome() === 'expected';
const failed = treeItem.test.results.length && treeItem.test.outcome() !== 'expected';
const running = treeItem.test.results.some(r => r.duration === -1);
if (running)
return 'codicon-loading';
if (ok)
return 'codicon-check';
if (failed)
return 'codicon-error';
} else {
return treeItem.expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
}
}}
itemIndent={(treeItem: TreeItem) => treeItem.kind === 'file' ? 0 : treeItem.kind === 'case' ? 1 : 2}
selectedItem={selectedTreeItem}
onAccepted={runTreeItem}
onLeftArrow={(treeItem: TreeItem) => {
if (treeItem.children && treeItem.expanded) {
expandedItems.set(treeItem.id, false);
setExpandedItems(new Map(expandedItems));
}}
showNoItemsMessage={true}></ListView>
{(rootSuite.value?.suites.length || 0) > 1 && <div style={{ flex: 'none', borderTop: '1px solid var(--vscode-panel-border)' }}>
<Toolbar>
<h3 className='title'>Projects</h3>
</Toolbar>
<ListView
items={rootSuite.value!.suites}
onSelected={(suite: Suite) => {
const copy = [...projectNames];
if (copy.includes(suite.title))
copy.splice(copy.indexOf(suite.title), 1);
else
copy.push(suite.title);
setProjectNames(copy);
}}
itemRender={(suite: Suite) => {
return <label style={{ display: 'flex', pointerEvents: 'none' }}>
<input type='checkbox' checked={projectNames.includes(suite.title)} />
{suite.title}
</label>;
}}
/>
</div>}
{isRunningTest && <div className='status-line'>
Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed
</div>}
{!isRunningTest && <div className='status-line'>
Total: {visibleTestIds.size} tests
</div>}
</div>
</SplitView>;
} else {
setSelectedTreeItemId(treeItem.parent?.id);
}
}}
onRightArrow={(treeItem: TreeItem) => {
if (treeItem.children) {
expandedItems.set(treeItem.id, true);
setExpandedItems(new Map(expandedItems));
}
}}
onSelected={(treeItem: TreeItem) => {
setSelectedTreeItemId(treeItem.id);
}}
onIconClicked={(treeItem: TreeItem) => {
if (treeItem.kind === 'test')
return;
if (treeItem.expanded)
expandedItems.set(treeItem.id, false);
else
expandedItems.set(treeItem.id, true);
setExpandedItems(new Map(expandedItems));
}}
noItemsMessage='No tests' />;
</div>;
};
export const TraceView: React.FC<{
testItem: TestItem | undefined,
isRunningTest: boolean,
}> = ({ testItem, isRunningTest }) => {
}> = ({ testItem }) => {
const [model, setModel] = React.useState<MultiTraceModel | undefined>();
const [stepsProgress, setStepsProgress] = React.useState(0);
updateStepsProgress = () => setStepsProgress(stepsProgress + 1);
@ -249,20 +272,9 @@ export const TraceView: React.FC<{
setModel(undefined);
}
})();
}, [testItem, isRunningTest, stepsProgress]);
}, [testItem, stepsProgress]);
if (!model) {
return <div className='vbox'>
<div className='drop-target'>
<div>Run test to see the trace</div>
<div style={{ paddingTop: 20 }}>
<div>Double click a test or hit Enter</div>
</div>
</div>
</div>;
}
return <Workbench model={model} />;
return <Workbench model={model}/>;
};
declare global {

View File

@ -31,25 +31,26 @@ import './workbench.css';
import { MetadataView } from './metadataView';
export const Workbench: React.FunctionComponent<{
model: MultiTraceModel,
model?: MultiTraceModel,
}> = ({ model }) => {
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>('logs');
const activeAction = highlightedAction || selectedAction;
const activeAction = model ? highlightedAction || selectedAction : undefined;
const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 };
const consoleCount = errors + warnings;
const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0;
const sdkLanguage = model?.sdkLanguage || 'javascript';
const tabs = [
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={activeAction} sdkLanguage={model.sdkLanguage} /> },
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} /> },
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={activeAction} /> },
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={activeAction} /> },
];
if (model.hasSource)
if (model?.hasSource)
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
return <div className='vbox'>
@ -60,27 +61,33 @@ export const Workbench: React.FunctionComponent<{
/>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={300} orientation='vertical'>
<SnapshotTab action={activeAction} sdkLanguage={model.sdkLanguage || 'javascript'} testIdAttributeName={model.testIdAttributeName || 'data-testid'} />
<SnapshotTab action={activeAction} sdkLanguage={sdkLanguage} testIdAttributeName={model?.testIdAttributeName || 'data-testid'} />
<TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab}/>
</SplitView>
<TabbedPane tabs={
[
{ id: 'actions', title: 'Actions', count: 0, render: () => <ActionList
sdkLanguage={model.sdkLanguage}
actions={model.actions}
selectedAction={selectedAction}
onSelected={action => {
setSelectedAction(action);
}}
onHighlighted={action => {
setHighlightedAction(action);
}}
revealConsole={() => setSelectedPropertiesTab('console')}
/> },
{ id: 'metadata',
{
id: 'actions',
title: 'Actions',
count: 0,
component: <ActionList
sdkLanguage={sdkLanguage}
actions={model?.actions || []}
selectedAction={model ? selectedAction : undefined}
onSelected={action => {
setSelectedAction(action);
}}
onHighlighted={action => {
setHighlightedAction(action);
}}
revealConsole={() => setSelectedPropertiesTab('console')}
/>
},
{
id: 'metadata',
title: 'Metadata',
count: 0,
render: () => <MetadataView model={model} />
component: <MetadataView model={model}/>
},
]
} selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/>

View File

@ -101,7 +101,6 @@ svg {
color: var(--green);
}
.codicon-close,
.codicon-error {
color: var(--red);
}

View File

@ -34,25 +34,24 @@
padding-left: 5px;
}
.list-view-entry.highlighted,
.list-view-entry.selected {
background-color: var(--vscode-list-inactiveSelectionBackground);
.list-view-entry.highlighted:not(.selected) {
background-color: var(--vscode-list-inactiveSelectionBackground) !important;
}
.list-view-entry.selected {
z-index: 10;
}
.list-view-entry.highlighted {
background-color: var(--vscode-list-inactiveSelectionBackground);
}
.list-view-content:focus .list-view-entry.selected:not(.error) {
.list-view-content:focus .list-view-entry.selected {
background-color: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
outline: 1px solid var(--vscode-focusBorder);
}
.list-view-content:focus .list-view-entry.selected * {
color: var(--vscode-list-activeSelectionForeground) !important;
}
.list-view-content:focus .list-view-entry.error.selected {
outline: 1px solid var(--vscode-inputValidation-errorBorder);
}

View File

@ -31,7 +31,7 @@ export type ListViewProps = {
onRightArrow?: (item: any) => void,
onHighlighted?: (item: any | undefined) => void,
onIconClicked?: (item: any) => void,
showNoItemsMessage?: boolean,
noItemsMessage?: string,
dataTestId?: string,
};
@ -49,7 +49,7 @@ export const ListView: React.FC<ListViewProps> = ({
onRightArrow,
onHighlighted,
onIconClicked,
showNoItemsMessage,
noItemsMessage,
dataTestId,
}) => {
const itemListRef = React.createRef<HTMLDivElement>();
@ -102,7 +102,7 @@ export const ListView: React.FC<ListViewProps> = ({
}}
ref={itemListRef}
>
{showNoItemsMessage && items.length === 0 && <div className='list-view-empty'>No items</div>}
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
{items.map((item, index) => <ListItemView
key={itemKey ? itemKey(item) : String(index)}
hasIcons={!!itemIcon}

View File

@ -22,31 +22,37 @@ export interface TabbedPaneTabModel {
id: string;
title: string | JSX.Element;
count?: number;
render: () => React.ReactElement;
component?: React.ReactElement;
render?: () => React.ReactElement;
}
export const TabbedPane: React.FunctionComponent<{
tabs: TabbedPaneTabModel[],
leftToolbar?: React.ReactElement[],
rightToolbar?: React.ReactElement[],
selectedTab: string,
setSelectedTab: (tab: string) => void
}> = ({ tabs, selectedTab, setSelectedTab }) => {
}> = ({ tabs, selectedTab, setSelectedTab, leftToolbar, rightToolbar }) => {
return <div className='tabbed-pane'>
<div className='vbox'>
<Toolbar>{
tabs.map(tab => (
<Toolbar>{[
...leftToolbar || [],
...tabs.map(tab => (
<TabbedPaneTab
id={tab.id}
title={tab.title}
count={tab.count}
selected={selectedTab === tab.id}
onSelect={setSelectedTab}
></TabbedPaneTab>
))
}</Toolbar>
></TabbedPaneTab>)),
...rightToolbar || [],
]}</Toolbar>
{
tabs.map(tab => {
if (tab.component)
return <div key={tab.id} className='tab-content' style={{ display: selectedTab === tab.id ? 'inherit' : 'none' }}>{tab.component}</div>;
if (selectedTab === tab.id)
return <div key={tab.id} className='tab-content'>{tab.render()}</div>;
return <div key={tab.id} className='tab-content'>{tab.component || tab.render!()}</div>;
})
}
</div>

View File

@ -45,7 +45,7 @@ class TraceViewerPage {
constructor(public page: Page) {
this.actionTitles = page.locator('.action-title');
this.callLines = page.locator('.call-line');
this.callLines = page.locator('.call-tab .call-line');
this.consoleLines = page.locator('.console-line');
this.consoleLineMessages = page.locator('.console-line-message');
this.consoleStacks = page.locator('.console-stack');