chore: add ui mode terminal (#21470)
This commit is contained in:
parent
9e7abb2a76
commit
e9f94f0346
|
|
@ -36,6 +36,7 @@ class UIMode {
|
||||||
globalCleanup: (() => Promise<FullResult['status']>) | undefined;
|
globalCleanup: (() => Promise<FullResult['status']>) | undefined;
|
||||||
private _watcher: FSWatcher | undefined;
|
private _watcher: FSWatcher | undefined;
|
||||||
private _watchTestFile: string | undefined;
|
private _watchTestFile: string | undefined;
|
||||||
|
private _originalStderr: (buffer: string | Uint8Array) => void;
|
||||||
|
|
||||||
constructor(config: FullConfigInternal) {
|
constructor(config: FullConfigInternal) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
|
@ -44,6 +45,15 @@ class UIMode {
|
||||||
p.retries = 0;
|
p.retries = 0;
|
||||||
config._internal.configCLIOverrides.use = config._internal.configCLIOverrides.use || {};
|
config._internal.configCLIOverrides.use = config._internal.configCLIOverrides.use || {};
|
||||||
config._internal.configCLIOverrides.use.trace = 'on';
|
config._internal.configCLIOverrides.use.trace = 'on';
|
||||||
|
this._originalStderr = process.stderr.write.bind(process.stderr);
|
||||||
|
process.stdout.write = (chunk: string | Buffer) => {
|
||||||
|
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stdout', chunk) });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
process.stderr.write = (chunk: string | Buffer) => {
|
||||||
|
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stderr', chunk) });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async runGlobalSetup(): Promise<FullResult['status']> {
|
async runGlobalSetup(): Promise<FullResult['status']> {
|
||||||
|
|
@ -78,6 +88,12 @@ class UIMode {
|
||||||
this._stopTests();
|
this._stopTests();
|
||||||
if (method === 'watch')
|
if (method === 'watch')
|
||||||
this._watchFile(params.fileName);
|
this._watchFile(params.fileName);
|
||||||
|
if (method === 'resizeTerminal') {
|
||||||
|
process.stdout.columns = params.cols;
|
||||||
|
process.stdout.rows = params.rows;
|
||||||
|
process.stderr.columns = params.cols;
|
||||||
|
process.stderr.columns = params.rows;
|
||||||
|
}
|
||||||
if (method === 'exit')
|
if (method === 'exit')
|
||||||
exitPromise.resolve();
|
exitPromise.resolve();
|
||||||
});
|
});
|
||||||
|
|
@ -86,7 +102,7 @@ class UIMode {
|
||||||
|
|
||||||
private _dispatchEvent(message: any) {
|
private _dispatchEvent(message: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
this._page.mainFrame().evaluateExpression(dispatchFuncSource, true, message).catch(e => console.log(e));
|
this._page.mainFrame().evaluateExpression(dispatchFuncSource, true, message).catch(e => this._originalStderr(String(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _listTests() {
|
private async _listTests() {
|
||||||
|
|
@ -156,3 +172,15 @@ export async function runUIMode(config: FullConfigInternal): Promise<FullResult[
|
||||||
await uiMode.showUI();
|
await uiMode.showUI();
|
||||||
return await uiMode.globalCleanup?.() || 'passed';
|
return await uiMode.globalCleanup?.() || 'passed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StdioPayload = {
|
||||||
|
type: 'stdout' | 'stderr';
|
||||||
|
text?: string;
|
||||||
|
buffer?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function chunkToPayload(type: 'stdout' | 'stderr', chunk: Buffer | string): StdioPayload {
|
||||||
|
if (chunk instanceof Buffer)
|
||||||
|
return { type, buffer: chunk.toString('base64') };
|
||||||
|
return { type, text: chunk };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,15 +51,12 @@
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.watch-mode-sidebar .spacer {
|
.status-line {
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.watch-mode-sidebar .status-line {
|
|
||||||
flex: none;
|
flex: none;
|
||||||
border-top: 1px solid var(--vscode-panel-border);
|
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
color: var(--vscode-statusBar-foreground);
|
||||||
|
background-color: var(--vscode-statusBar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view-entry:not(.selected):not(.highlighted) .toolbar-button {
|
.list-view-entry:not(.selected):not(.highlighted) .toolbar-button {
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,21 @@ import { Toolbar } from '@web/components/toolbar';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import type { ContextEntry } from '../entries';
|
import type { ContextEntry } from '../entries';
|
||||||
import type * as trace from '@trace/trace';
|
import type * as trace from '@trace/trace';
|
||||||
|
import type { XtermDataSource } from '@web/components/xtermWrapper';
|
||||||
|
import { XtermWrapper } from '@web/components/xtermWrapper';
|
||||||
|
|
||||||
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
||||||
let updateStepsProgress: () => void = () => {};
|
let updateStepsProgress: () => void = () => {};
|
||||||
let runWatchedTests = () => {};
|
let runWatchedTests = () => {};
|
||||||
let runVisibleTests = () => {};
|
let runVisibleTests = () => {};
|
||||||
|
|
||||||
|
const xtermDataSource: XtermDataSource = {
|
||||||
|
pending: [],
|
||||||
|
clear: () => {},
|
||||||
|
write: data => xtermDataSource.pending.push(data),
|
||||||
|
resize: (cols: number, rows: number) => sendMessageNoReply('resizeTerminal', { cols, rows }),
|
||||||
|
};
|
||||||
|
|
||||||
export const WatchModeView: React.FC<{}> = ({
|
export const WatchModeView: React.FC<{}> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [projectNames, setProjectNames] = React.useState<string[]>([]);
|
const [projectNames, setProjectNames] = React.useState<string[]>([]);
|
||||||
|
|
@ -64,54 +73,31 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
setProjectNames([rootSuite.value?.suites[0].title]);
|
setProjectNames([rootSuite.value?.suites[0].title]);
|
||||||
}, [projectNames, rootSuite]);
|
}, [projectNames, rootSuite]);
|
||||||
|
|
||||||
return <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
return <div className='vbox'>
|
||||||
<TraceView testItem={selectedTestItem}></TraceView>
|
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<div className='vbox watch-mode-sidebar'>
|
<TraceView testItem={selectedTestItem}></TraceView>
|
||||||
<Toolbar>
|
<div className='vbox watch-mode-sidebar'>
|
||||||
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
|
<Toolbar>
|
||||||
<ToolbarButton icon='play' title='Run' onClick={runVisibleTests} disabled={isRunningTest}></ToolbarButton>
|
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
|
||||||
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
<ToolbarButton icon='play' title='Run' onClick={runVisibleTests} disabled={isRunningTest}></ToolbarButton>
|
||||||
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
|
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
||||||
<div className='spacer'></div>
|
<ToolbarButton icon='refresh' title='Reload' onClick={resetCollectingRootSuite} disabled={isRunningTest}></ToolbarButton>
|
||||||
<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>
|
<div className='spacer'></div>
|
||||||
<ToolbarButton icon='close' title='Close settings' toggled={false} onClick={() => setSettingsVisible(false)}></ToolbarButton>
|
<ToolbarButton icon='gear' title='Toggle color mode' toggled={settingsVisible} onClick={() => { setSettingsVisible(!settingsVisible); }}></ToolbarButton>
|
||||||
</div>
|
</Toolbar>
|
||||||
{(rootSuite.value?.suites || []).map(suite => {
|
{ !settingsVisible && <TestList
|
||||||
return <div style={{ display: 'flex', alignItems: 'center', lineHeight: '24px' }}>
|
projectNames={projectNames}
|
||||||
<input id={`project-${suite.title}`} type='checkbox' checked={projectNames.includes(suite.title)} onClick={() => {
|
rootSuite={rootSuite}
|
||||||
const copy = [...projectNames];
|
isRunningTest={isRunningTest}
|
||||||
if (copy.includes(suite.title))
|
runTests={runTests}
|
||||||
copy.splice(copy.indexOf(suite.title), 1);
|
onTestItemSelected={setSelectedTestItem} />}
|
||||||
else
|
{settingsVisible && <SettingsView projectNames={projectNames} setProjectNames={setProjectNames} onClose={() => setSettingsVisible(false)}></SettingsView>}
|
||||||
copy.push(suite.title);
|
</div>
|
||||||
setProjectNames(copy);
|
</SplitView>
|
||||||
}} style={{ margin: '0 5px 0 10px' }} />
|
<div className='status-line'>
|
||||||
<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
|
Running: {progress.total} tests | {progress.passed} passed | {progress.failed} failed
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</SplitView>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TestList: React.FC<{
|
export const TestList: React.FC<{
|
||||||
|
|
@ -243,7 +229,40 @@ export const TestList: React.FC<{
|
||||||
expandedItems.set(treeItem.id, true);
|
expandedItems.set(treeItem.id, true);
|
||||||
setExpandedItems(new Map(expandedItems));
|
setExpandedItems(new Map(expandedItems));
|
||||||
}}
|
}}
|
||||||
noItemsMessage='No tests' />;
|
noItemsMessage='No tests' />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsView: React.FC<{
|
||||||
|
projectNames: string[],
|
||||||
|
setProjectNames: (projectNames: string[]) => void,
|
||||||
|
onClose: () => void,
|
||||||
|
}> = ({ projectNames, setProjectNames, onClose }) => {
|
||||||
|
return <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={onClose}></ToolbarButton>
|
||||||
|
</div>
|
||||||
|
{projectNames.map(projectName => {
|
||||||
|
return <div style={{ display: 'flex', alignItems: 'center', lineHeight: '24px' }}>
|
||||||
|
<input id={`project-${projectName}`} type='checkbox' checked={projectNames.includes(projectName)} onClick={() => {
|
||||||
|
const copy = [...projectNames];
|
||||||
|
if (copy.includes(projectName))
|
||||||
|
copy.splice(copy.indexOf(projectName), 1);
|
||||||
|
else
|
||||||
|
copy.push(projectName);
|
||||||
|
setProjectNames(copy);
|
||||||
|
}} style={{ margin: '0 5px 0 10px' }} />
|
||||||
|
<label htmlFor={`project-${projectName}`}>
|
||||||
|
{projectName}
|
||||||
|
</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>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -274,7 +293,10 @@ export const TraceView: React.FC<{
|
||||||
})();
|
})();
|
||||||
}, [testItem, stepsProgress]);
|
}, [testItem, stepsProgress]);
|
||||||
|
|
||||||
return <Workbench model={model}/>;
|
const xterm = <XtermWrapper source={xtermDataSource}></XtermWrapper>;
|
||||||
|
return <Workbench model={model} output={xterm} rightToolbar={[
|
||||||
|
<ToolbarButton icon='trash' title='Clear output' onClick={() => xtermDataSource.clear()}></ToolbarButton>,
|
||||||
|
]}/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
@ -325,10 +347,18 @@ const resetCollectingRootSuite = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
(window as any).dispatch = (message: any) => {
|
(window as any).dispatch = (message: any) => {
|
||||||
if (message.method === 'fileChanged')
|
if (message.method === 'fileChanged') {
|
||||||
runWatchedTests();
|
runWatchedTests();
|
||||||
else
|
} else if (message.method === 'stdio') {
|
||||||
|
if (message.params.buffer) {
|
||||||
|
const data = atob(message.params.buffer);
|
||||||
|
xtermDataSource.write(data);
|
||||||
|
} else {
|
||||||
|
xtermDataSource.write(message.params.text);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
receiver?.dispatch(message);
|
receiver?.dispatch(message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendMessage = async (method: string, params: any) => {
|
const sendMessage = async (method: string, params: any) => {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,3 @@
|
||||||
.workbench .header .title {
|
.workbench .header .title {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench .spacer {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,20 @@ import { NetworkTab } from './networkTab';
|
||||||
import { SnapshotTab } from './snapshotTab';
|
import { SnapshotTab } from './snapshotTab';
|
||||||
import { SourceTab } from './sourceTab';
|
import { SourceTab } from './sourceTab';
|
||||||
import { TabbedPane } from '@web/components/tabbedPane';
|
import { TabbedPane } from '@web/components/tabbedPane';
|
||||||
|
import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
||||||
import { Timeline } from './timeline';
|
import { Timeline } from './timeline';
|
||||||
import './workbench.css';
|
import './workbench.css';
|
||||||
import { MetadataView } from './metadataView';
|
import { MetadataView } from './metadataView';
|
||||||
|
|
||||||
export const Workbench: React.FunctionComponent<{
|
export const Workbench: React.FunctionComponent<{
|
||||||
model?: MultiTraceModel,
|
model?: MultiTraceModel,
|
||||||
}> = ({ model }) => {
|
output?: React.ReactElement,
|
||||||
|
rightToolbar?: React.ReactElement[],
|
||||||
|
}> = ({ model, output, rightToolbar }) => {
|
||||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||||
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
|
||||||
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>('logs');
|
const [selectedPropertiesTab, setSelectedPropertiesTab] = React.useState<string>(output ? 'output' : 'call');
|
||||||
const activeAction = model ? highlightedAction || selectedAction : undefined;
|
const activeAction = model ? highlightedAction || selectedAction : undefined;
|
||||||
|
|
||||||
const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 };
|
const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 };
|
||||||
|
|
@ -44,14 +47,15 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0;
|
const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0;
|
||||||
const sdkLanguage = model?.sdkLanguage || 'javascript';
|
const sdkLanguage = model?.sdkLanguage || 'javascript';
|
||||||
|
|
||||||
const tabs = [
|
const tabs: TabbedPaneTabModel[] = [
|
||||||
{ id: 'logs', title: 'Call', count: 0, render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} /> },
|
{ id: 'call', title: 'Call', render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} /> },
|
||||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={activeAction} /> },
|
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={activeAction} /> },
|
||||||
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={activeAction} /> },
|
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={activeAction} /> },
|
||||||
|
{ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (model?.hasSource)
|
if (output)
|
||||||
tabs.push({ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> });
|
tabs.unshift({ id: 'output', title: 'Output', component: output });
|
||||||
|
|
||||||
return <div className='vbox'>
|
return <div className='vbox'>
|
||||||
<Timeline
|
<Timeline
|
||||||
|
|
@ -59,38 +63,38 @@ export const Workbench: React.FunctionComponent<{
|
||||||
selectedAction={activeAction}
|
selectedAction={activeAction}
|
||||||
onSelected={action => setSelectedAction(action)}
|
onSelected={action => setSelectedAction(action)}
|
||||||
/>
|
/>
|
||||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
<SplitView sidebarSize={output ? 250 : 350} orientation={output ? 'vertical' : 'horizontal'}>
|
||||||
<SplitView sidebarSize={300} orientation='vertical'>
|
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
<SnapshotTab action={activeAction} sdkLanguage={sdkLanguage} testIdAttributeName={model?.testIdAttributeName || 'data-testid'} />
|
<SnapshotTab action={activeAction} sdkLanguage={sdkLanguage} testIdAttributeName={model?.testIdAttributeName || 'data-testid'} />
|
||||||
<TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab}/>
|
<TabbedPane tabs={
|
||||||
|
[
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
component: <MetadataView model={model}/>
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
<TabbedPane tabs={
|
<TabbedPane tabs={tabs} selectedTab={selectedPropertiesTab} setSelectedTab={setSelectedPropertiesTab} rightToolbar={rightToolbar}/>
|
||||||
[
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
component: <MetadataView model={model}/>
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} selectedTab={selectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}/>
|
|
||||||
</SplitView>
|
</SplitView>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,10 @@ svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.codicon-check {
|
.codicon-check {
|
||||||
color: var(--green);
|
color: var(--green);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,11 @@ const ListItemView: React.FC<{
|
||||||
ref={divRef}
|
ref={divRef}
|
||||||
>
|
>
|
||||||
{indent ? <div style={{ minWidth: indent * 16 }}></div> : undefined}
|
{indent ? <div style={{ minWidth: indent * 16 }}></div> : undefined}
|
||||||
{hasIcons && <div className={'codicon ' + (icon || 'blank')} style={{ minWidth: 16, marginRight: 4 }} onClick={onIconClicked}></div>}
|
{hasIcons && <div className={'codicon ' + (icon || 'blank')} style={{ minWidth: 16, marginRight: 4 }} onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onIconClicked();
|
||||||
|
}}></div>}
|
||||||
{typeof children === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{children}</div> : children}
|
{typeof children === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{children}</div> : children}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ export const TabbedPane: React.FunctionComponent<{
|
||||||
selected={selectedTab === tab.id}
|
selected={selectedTab === tab.id}
|
||||||
onSelect={setSelectedTab}
|
onSelect={setSelectedTab}
|
||||||
></TabbedPaneTab>)),
|
></TabbedPaneTab>)),
|
||||||
|
<div className='spacer'></div>,
|
||||||
...rightToolbar || [],
|
...rightToolbar || [],
|
||||||
]}</Toolbar>
|
]}</Toolbar>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@
|
||||||
box-shadow: var(--box-shadow);
|
box-shadow: var(--box-shadow);
|
||||||
background-color: var(--vscode-sideBar-background);
|
background-color: var(--vscode-sideBar-background);
|
||||||
color: var(--vscode-sideBarTitle-foreground);
|
color: var(--vscode-sideBarTitle-foreground);
|
||||||
min-height: 32px;
|
min-height: 35px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-linewrap {
|
.toolbar-linewrap {
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar input {
|
.toolbar input {
|
||||||
padding: 0 10px;
|
padding: 0 5px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@ import type { Terminal } from 'xterm';
|
||||||
import type { XtermModule } from './xtermModule';
|
import type { XtermModule } from './xtermModule';
|
||||||
import { isDarkTheme } from '@web/theme';
|
import { isDarkTheme } from '@web/theme';
|
||||||
|
|
||||||
export type XTermDataSource = {
|
export type XtermDataSource = {
|
||||||
pending: (string | Uint8Array)[];
|
pending: (string | Uint8Array)[];
|
||||||
|
clear: () => void,
|
||||||
write: (data: string | Uint8Array) => void;
|
write: (data: string | Uint8Array) => void;
|
||||||
resize: (cols: number, rows: number) => void;
|
resize: (cols: number, rows: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const XTermWrapper: React.FC<{ source: XTermDataSource }> = ({
|
export const XtermWrapper: React.FC<{ source: XtermDataSource }> = ({
|
||||||
source
|
source
|
||||||
}) => {
|
}) => {
|
||||||
const xtermElement = React.createRef<HTMLDivElement>();
|
const xtermElement = React.createRef<HTMLDivElement>();
|
||||||
|
|
@ -55,8 +56,13 @@ export const XTermWrapper: React.FC<{ source: XTermDataSource }> = ({
|
||||||
for (const p of source.pending)
|
for (const p of source.pending)
|
||||||
newTerminal.write(p);
|
newTerminal.write(p);
|
||||||
source.write = (data => {
|
source.write = (data => {
|
||||||
|
source.pending.push(data);
|
||||||
newTerminal.write(data);
|
newTerminal.write(data);
|
||||||
});
|
});
|
||||||
|
source.clear = () => {
|
||||||
|
source.pending = [];
|
||||||
|
newTerminal.clear();
|
||||||
|
};
|
||||||
newTerminal.open(element);
|
newTerminal.open(element);
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
setTerminal(newTerminal);
|
setTerminal(newTerminal);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue