refactor(ui): some react refactorings (#31900)
Addresses https://github.com/microsoft/playwright/issues/31863. This PR is chonky, but the individual commits should be easy to review. If they're not, i'm happy to break them out into individual PRs. There's two main things this does: 1. Remove some unused imports 2. Add a `clsx`-inspired helper function for classname templating I wasn't able to replace `ReactDOM.render` with `ReactDOM.createRoot`. This is the new recommended way starting with React 18, and the existing one is going to be deprecated at some point. But it somehow breaks our tests, i'll have to investigate that separately.
This commit is contained in:
parent
64fe245297
commit
99724d0322
|
|
@ -19,6 +19,7 @@ import './chip.css';
|
||||||
import './colors.css';
|
import './colors.css';
|
||||||
import './common.css';
|
import './common.css';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export const Chip: React.FC<{
|
export const Chip: React.FC<{
|
||||||
header: JSX.Element | string,
|
header: JSX.Element | string,
|
||||||
|
|
@ -31,14 +32,14 @@ export const Chip: React.FC<{
|
||||||
}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId, targetRef }) => {
|
}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId, targetRef }) => {
|
||||||
return <div className='chip' data-testid={dataTestId} ref={targetRef}>
|
return <div className='chip' data-testid={dataTestId} ref={targetRef}>
|
||||||
<div
|
<div
|
||||||
className={'chip-header' + (setExpanded ? ' expanded-' + expanded : '')}
|
className={clsx('chip-header', setExpanded && ' expanded-' + expanded)}
|
||||||
onClick={() => setExpanded?.(!expanded)}
|
onClick={() => setExpanded?.(!expanded)}
|
||||||
title={typeof header === 'string' ? header : undefined}>
|
title={typeof header === 'string' ? header : undefined}>
|
||||||
{setExpanded && !!expanded && icons.downArrow()}
|
{setExpanded && !!expanded && icons.downArrow()}
|
||||||
{setExpanded && !expanded && icons.rightArrow()}
|
{setExpanded && !expanded && icons.rightArrow()}
|
||||||
{header}
|
{header}
|
||||||
</div>
|
</div>
|
||||||
{(!setExpanded || expanded) && <div className={'chip-body' + (noInsets ? ' chip-body-no-insets' : '')}>{children}</div>}
|
{(!setExpanded || expanded) && <div className={clsx('chip-body', noInsets && 'chip-body-no-insets')}>{children}</div>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { TreeItem } from './treeItem';
|
||||||
import { CopyToClipboard } from './copyToClipboard';
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
import './links.css';
|
import './links.css';
|
||||||
import { linkifyText } from './renderUtils';
|
import { linkifyText } from './renderUtils';
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export function navigate(href: string) {
|
export function navigate(href: string) {
|
||||||
window.history.pushState({}, '', href);
|
window.history.pushState({}, '', href);
|
||||||
|
|
@ -48,8 +49,8 @@ export const Link: React.FunctionComponent<{
|
||||||
className?: string,
|
className?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
children: any,
|
children: any,
|
||||||
}> = ({ href, click, ctrlClick, className, children, title }) => {
|
}> = ({ click, ctrlClick, children, ...rest }) => {
|
||||||
return <a style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} href={href} className={`${className || ''}`} title={title} onClick={e => {
|
return <a {...rest} style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} onClick={e => {
|
||||||
if (click) {
|
if (click) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click);
|
navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click);
|
||||||
|
|
@ -64,7 +65,7 @@ export const ProjectLink: React.FunctionComponent<{
|
||||||
const encoded = encodeURIComponent(projectName);
|
const encoded = encodeURIComponent(projectName);
|
||||||
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
||||||
return <Link href={`#?q=p:${value}`}>
|
return <Link href={`#?q=p:${value}`}>
|
||||||
<span className={'label label-color-' + (projectNames.indexOf(projectName) % 6)} style={{ margin: '6px 0 0 6px' }}>
|
<span className={clsx('label', `label-color-${projectNames.indexOf(projectName) % 6}`)} style={{ margin: '6px 0 0 6px' }}>
|
||||||
{projectName}
|
{projectName}
|
||||||
</span>
|
</span>
|
||||||
</Link>;
|
</Link>;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
import './tabbedPane.css';
|
import './tabbedPane.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ export const TabbedPane: React.FunctionComponent<{
|
||||||
<div className='hbox' style={{ flex: 'none' }}>
|
<div className='hbox' style={{ flex: 'none' }}>
|
||||||
<div className='tabbed-pane-tab-strip'>{
|
<div className='tabbed-pane-tab-strip'>{
|
||||||
tabs.map(tab => (
|
tabs.map(tab => (
|
||||||
<div className={'tabbed-pane-tab-element ' + (selectedTab === tab.id ? 'selected' : '')}
|
<div className={clsx('tabbed-pane-tab-element', selectedTab === tab.id && 'selected')}
|
||||||
onClick={() => setSelectedTab(tab.id)}
|
onClick={() => setSelectedTab(tab.id)}
|
||||||
key={tab.id}>
|
key={tab.id}>
|
||||||
<div className='tabbed-pane-tab-label'>{tab.title}</div>
|
<div className='tabbed-pane-tab-label'>{tab.title}</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { test, expect } from '@playwright/experimental-ct-react';
|
import { test, expect } from '@playwright/experimental-ct-react';
|
||||||
import { TestCaseView } from './testCaseView';
|
import { TestCaseView } from './testCaseView';
|
||||||
import type { TestCase, TestResult } from './types';
|
import type { TestCase, TestResult } from './types';
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import './testCaseView.css';
|
||||||
import { TestResultView } from './testResultView';
|
import { TestResultView } from './testResultView';
|
||||||
import { linkifyText } from './renderUtils';
|
import { linkifyText } from './renderUtils';
|
||||||
import { hashStringToInt, msToString } from './utils';
|
import { hashStringToInt, msToString } from './utils';
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export const TestCaseView: React.FC<{
|
export const TestCaseView: React.FC<{
|
||||||
projectNames: string[],
|
projectNames: string[],
|
||||||
|
|
@ -90,7 +91,7 @@ const LabelsLinkView: React.FC<React.PropsWithChildren<{
|
||||||
<>
|
<>
|
||||||
{labels.map(label => (
|
{labels.map(label => (
|
||||||
<a key={label} style={{ textDecoration: 'none', color: 'var(--color-fg-default)' }} href={`#?q=${label}`} >
|
<a key={label} style={{ textDecoration: 'none', color: 'var(--color-fg-default)' }} href={`#?q=${label}`} >
|
||||||
<span style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={'label label-color-' + (hashStringToInt(label))}>
|
<span style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={clsx('label', 'label-color-' + hashStringToInt(label))}>
|
||||||
{label.slice(1)}
|
{label.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { generateTraceUrl, Link, navigate, ProjectLink } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
import './testFileView.css';
|
import './testFileView.css';
|
||||||
import { video, image, trace } from './icons';
|
import { video, image, trace } from './icons';
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export const TestFileView: React.FC<React.PropsWithChildren<{
|
export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||||
report: HTMLReport;
|
report: HTMLReport;
|
||||||
|
|
@ -39,7 +40,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||||
{file.fileName}
|
{file.fileName}
|
||||||
</span>}>
|
</span>}>
|
||||||
{file.tests.filter(t => filter.matches(t)).map(test =>
|
{file.tests.filter(t => filter.matches(t)).map(test =>
|
||||||
<div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}>
|
<div key={`test-${test.testId}`} className={clsx('test-file-test', 'test-file-test-outcome-' + test.outcome)}>
|
||||||
<div className='hbox' style={{ alignItems: 'flex-start' }}>
|
<div className='hbox' style={{ alignItems: 'flex-start' }}>
|
||||||
<div className='hbox'>
|
<div className='hbox'>
|
||||||
<span className='test-file-test-status-icon'>
|
<span className='test-file-test-status-icon'>
|
||||||
|
|
@ -101,7 +102,7 @@ const LabelsClickView: React.FC<React.PropsWithChildren<{
|
||||||
return labels.length > 0 ? (
|
return labels.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{labels.map(label => (
|
{labels.map(label => (
|
||||||
<span key={label} style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={'label label-color-' + (hashStringToInt(label))} onClick={e => onClickHandle(e, label)}>
|
<span key={label} style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={clsx('label', 'label-color-' + hashStringToInt(label))} onClick={e => onClickHandle(e, label)}>
|
||||||
{label.slice(1)}
|
{label.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import './callLog.css';
|
import './callLog.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { CallLog } from './recorderTypes';
|
import type { CallLog } from './recorderTypes';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { clsx, msToString } from '@web/uiUtils';
|
||||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
|
|
||||||
|
|
@ -53,9 +53,9 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||||
titlePrefix = callLog.title + '(';
|
titlePrefix = callLog.title + '(';
|
||||||
titleSuffix = ')';
|
titleSuffix = ')';
|
||||||
}
|
}
|
||||||
return <div className={`call-log-call ${callLog.status}`} key={callLog.id}>
|
return <div className={clsx('call-log-call', callLog.status)} key={callLog.id}>
|
||||||
<div className='call-log-call-header'>
|
<div className='call-log-call-header'>
|
||||||
<span className={`codicon codicon-chevron-${isExpanded ? 'down' : 'right'}`} style={{ cursor: 'pointer' }}onClick={() => {
|
<span className={clsx('codicon', `codicon-chevron-${isExpanded ? 'down' : 'right'}`)} style={{ cursor: 'pointer' }}onClick={() => {
|
||||||
const newOverrides = new Map(expandOverrides);
|
const newOverrides = new Map(expandOverrides);
|
||||||
newOverrides.set(callLog.id, !isExpanded);
|
newOverrides.set(callLog.id, !isExpanded);
|
||||||
setExpandOverrides(newOverrides);
|
setExpandOverrides(newOverrides);
|
||||||
|
|
@ -64,7 +64,7 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||||
{ callLog.params.url ? <span className='call-log-details'><span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span></span> : undefined }
|
{ callLog.params.url ? <span className='call-log-details'><span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span></span> : undefined }
|
||||||
{ locator ? <span className='call-log-details'><span className='call-log-selector' title={`page.${locator}`}>{`page.${locator}`}</span></span> : undefined }
|
{ locator ? <span className='call-log-details'><span className='call-log-selector' title={`page.${locator}`}>{`page.${locator}`}</span></span> : undefined }
|
||||||
{ titleSuffix }
|
{ titleSuffix }
|
||||||
<span className={'codicon ' + iconClass(callLog)}></span>
|
<span className={clsx('codicon', iconClass(callLog))}></span>
|
||||||
{ typeof callLog.duration === 'number' ? <span className='call-log-time'>— {msToString(callLog.duration)}</span> : undefined}
|
{ typeof callLog.duration === 'number' ? <span className='call-log-time'>— {msToString(callLog.duration)}</span> : undefined}
|
||||||
</div>
|
</div>
|
||||||
{ (isExpanded ? callLog.messages : []).map((message, i) => {
|
{ (isExpanded ? callLog.messages : []).map((message, i) => {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
import '@web/common.css';
|
import '@web/common.css';
|
||||||
import { applyTheme } from '@web/theme';
|
import { applyTheme } from '@web/theme';
|
||||||
import '@web/third_party/vscode/codicon.css';
|
import '@web/third_party/vscode/codicon.css';
|
||||||
import React from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { EmbeddedWorkbenchLoader } from './ui/embeddedWorkbenchLoader';
|
import { EmbeddedWorkbenchLoader } from './ui/embeddedWorkbenchLoader';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
import '@web/common.css';
|
import '@web/common.css';
|
||||||
import { applyTheme } from '@web/theme';
|
import { applyTheme } from '@web/theme';
|
||||||
import '@web/third_party/vscode/codicon.css';
|
import '@web/third_party/vscode/codicon.css';
|
||||||
import React from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { WorkbenchLoader } from './ui/workbenchLoader';
|
import { WorkbenchLoader } from './ui/workbenchLoader';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import type { SerializedValue } from '@protocol/channels';
|
import type { SerializedValue } from '@protocol/channels';
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent } from '@trace/trace';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { clsx, msToString } from '@web/uiUtils';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './callTab.css';
|
import './callTab.css';
|
||||||
import { CopyToClipboard } from './copyToClipboard';
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
|
|
@ -71,7 +71,7 @@ function renderProperty(property: Property, key: string) {
|
||||||
text = `"${text}"`;
|
text = `"${text}"`;
|
||||||
return (
|
return (
|
||||||
<div key={key} className='call-line'>
|
<div key={key} className='call-line'>
|
||||||
{property.name}:<span className={`call-value ${property.type}`} title={property.text}>{text}</span>
|
{property.name}:<span className={clsx('call-value', property.type)} title={property.text}>{text}</span>
|
||||||
{ ['string', 'number', 'object', 'locator'].includes(property.type) &&
|
{ ['string', 'number', 'object', 'locator'].includes(property.type) &&
|
||||||
<CopyToClipboard value={property.text} />
|
<CopyToClipboard value={property.text} />
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import './consoleTab.css';
|
||||||
import type * as modelUtil from './modelUtil';
|
import type * as modelUtil from './modelUtil';
|
||||||
import { ListView } from '@web/components/listView';
|
import { ListView } from '@web/components/listView';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { clsx, msToString } from '@web/uiUtils';
|
||||||
import { ansi2html } from '@web/ansi2html';
|
import { ansi2html } from '@web/ansi2html';
|
||||||
import { PlaceholderPanel } from './placeholderPanel';
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
|
|
||||||
|
|
@ -124,8 +124,8 @@ export const ConsoleTab: React.FunctionComponent<{
|
||||||
render={entry => {
|
render={entry => {
|
||||||
const timestamp = msToString(entry.timestamp - boundaries.minimum);
|
const timestamp = msToString(entry.timestamp - boundaries.minimum);
|
||||||
const timestampElement = <span className='console-time'>{timestamp}</span>;
|
const timestampElement = <span className='console-time'>{timestamp}</span>;
|
||||||
const errorSuffix = entry.isError ? ' status-error' : entry.isWarning ? ' status-warning' : ' status-none';
|
const errorSuffix = entry.isError ? 'status-error' : entry.isWarning ? 'status-warning' : 'status-none';
|
||||||
const statusElement = entry.browserMessage || entry.browserError ? <span className={'codicon codicon-browser' + errorSuffix} title='Browser message'></span> : <span className={'codicon codicon-file' + errorSuffix} title='Runner message'></span>;
|
const statusElement = entry.browserMessage || entry.browserError ? <span className={clsx('codicon', 'codicon-browser', errorSuffix)} title='Browser message'></span> : <span className={clsx('codicon', 'codicon-file', errorSuffix)} title='Runner message'></span>;
|
||||||
let locationText: string | undefined;
|
let locationText: string | undefined;
|
||||||
let messageBody: JSX.Element[] | string | undefined;
|
let messageBody: JSX.Element[] | string | undefined;
|
||||||
let messageInnerHTML: string | undefined;
|
let messageInnerHTML: string | undefined;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import type { ActionTraceEvent } from '@trace/trace';
|
||||||
import { context, prevInList } from './modelUtil';
|
import { context, prevInList } from './modelUtil';
|
||||||
import { Toolbar } from '@web/components/toolbar';
|
import { Toolbar } from '@web/components/toolbar';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { useMeasure } from '@web/uiUtils';
|
import { clsx, useMeasure } from '@web/uiUtils';
|
||||||
import { InjectedScript } from '@injected/injectedScript';
|
import { InjectedScript } from '@injected/injectedScript';
|
||||||
import { Recorder } from '@injected/recorder/recorder';
|
import { Recorder } from '@injected/recorder/recorder';
|
||||||
import ConsoleAPI from '@injected/consoleApi';
|
import ConsoleAPI from '@injected/consoleApi';
|
||||||
|
|
@ -209,8 +209,8 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||||
}}>
|
}}>
|
||||||
<BrowserFrame url={snapshotInfo.url} />
|
<BrowserFrame url={snapshotInfo.url} />
|
||||||
<div className='snapshot-switcher'>
|
<div className='snapshot-switcher'>
|
||||||
<iframe ref={iframeRef0} name='snapshot' title='DOM Snapshot' className={loadingRef.current.visibleIframe === 0 ? 'snapshot-visible' : ''}></iframe>
|
<iframe ref={iframeRef0} name='snapshot' title='DOM Snapshot' className={clsx(loadingRef.current.visibleIframe === 0 && 'snapshot-visible')}></iframe>
|
||||||
<iframe ref={iframeRef1} name='snapshot' title='DOM Snapshot' className={loadingRef.current.visibleIframe === 1 ? 'snapshot-visible' : ''}></iframe>
|
<iframe ref={iframeRef1} name='snapshot' title='DOM Snapshot' className={clsx(loadingRef.current.visibleIframe === 1 && 'snapshot-visible')}></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
import './tag.css';
|
import './tag.css';
|
||||||
|
|
||||||
export const TagView: React.FC<{ tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ tag, style, onClick }) => {
|
export const TagView: React.FC<{ tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ tag, style, onClick }) => {
|
||||||
return <span
|
return <span
|
||||||
className={`tag tag-color-${tagNameToColor(tag)}`}
|
className={clsx('tag', `tag-color-${tagNameToColor(tag)}`)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ margin: '6px 0 0 6px', ...style }}
|
style={{ margin: '6px 0 0 6px', ...style }}
|
||||||
title={`Click to filter by tag: ${tag}`}
|
title={`Click to filter by tag: ${tag}`}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { msToString, useMeasure } from '@web/uiUtils';
|
import { clsx, msToString, useMeasure } from '@web/uiUtils';
|
||||||
import { GlassPane } from '@web/shared/glassPane';
|
import { GlassPane } from '@web/shared/glassPane';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
|
|
@ -252,11 +252,12 @@ export const Timeline: React.FunctionComponent<{
|
||||||
<div className='timeline-bars'>{
|
<div className='timeline-bars'>{
|
||||||
bars.map((bar, index) => {
|
bars.map((bar, index) => {
|
||||||
return <div key={index}
|
return <div key={index}
|
||||||
className={'timeline-bar' + (bar.action ? ' action' : '')
|
className={clsx('timeline-bar',
|
||||||
+ (bar.resource ? ' network' : '')
|
bar.action && 'action',
|
||||||
+ (bar.consoleMessage ? ' console-message' : '')
|
bar.resource && 'network',
|
||||||
+ (bar.active ? ' active' : '')
|
bar.consoleMessage && 'console-message',
|
||||||
+ (bar.error ? ' error' : '')}
|
bar.active && 'active',
|
||||||
|
bar.error && 'error')}
|
||||||
style={{
|
style={{
|
||||||
left: bar.leftPosition,
|
left: bar.leftPosition,
|
||||||
width: Math.max(5, bar.rightPosition - bar.leftPosition),
|
width: Math.max(5, bar.rightPosition - bar.leftPosition),
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { Toolbar } from '@web/components/toolbar';
|
||||||
import type { XtermDataSource } from '@web/components/xtermWrapper';
|
import type { XtermDataSource } from '@web/components/xtermWrapper';
|
||||||
import { XtermWrapper } from '@web/components/xtermWrapper';
|
import { XtermWrapper } from '@web/components/xtermWrapper';
|
||||||
import { useDarkModeSetting } from '@web/theme';
|
import { useDarkModeSetting } from '@web/theme';
|
||||||
import { settings, useSetting } from '@web/uiUtils';
|
import { clsx, settings, useSetting } from '@web/uiUtils';
|
||||||
import { statusEx, TestTree } from '@testIsomorphic/testTree';
|
import { statusEx, TestTree } from '@testIsomorphic/testTree';
|
||||||
import type { TreeItem } from '@testIsomorphic/testTree';
|
import type { TreeItem } from '@testIsomorphic/testTree';
|
||||||
import { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
import { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
||||||
|
|
@ -435,7 +435,7 @@ export const UIModeView: React.FC<{}> = ({
|
||||||
</div>}
|
</div>}
|
||||||
<SplitView sidebarSize={250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
|
<SplitView sidebarSize={250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
|
||||||
<div className='vbox'>
|
<div className='vbox'>
|
||||||
<div className={'vbox' + (isShowingOutput ? '' : ' hidden')}>
|
<div className={clsx('vbox', !isShowingOutput && 'hidden')}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<div className='section-title' style={{ flex: 'none' }}>Output</div>
|
<div className='section-title' style={{ flex: 'none' }}>Output</div>
|
||||||
<ToolbarButton icon='circle-slash' title='Clear output' onClick={() => xtermDataSource.clear()}></ToolbarButton>
|
<ToolbarButton icon='circle-slash' title='Clear output' onClick={() => xtermDataSource.clear()}></ToolbarButton>
|
||||||
|
|
@ -444,7 +444,7 @@ export const UIModeView: React.FC<{}> = ({
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<XtermWrapper source={xtermDataSource}></XtermWrapper>
|
<XtermWrapper source={xtermDataSource}></XtermWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className={'vbox' + (isShowingOutput ? ' hidden' : '')}>
|
<div className={clsx('vbox', isShowingOutput && 'hidden')}>
|
||||||
<TraceView
|
<TraceView
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
rootDir={testModel?.config?.rootDir}
|
rootDir={testModel?.config?.rootDir}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import { AttachmentsTab } from './attachmentsTab';
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
import { InspectorTab } from './inspectorTab';
|
import { InspectorTab } from './inspectorTab';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { useSetting, msToString } from '@web/uiUtils';
|
import { useSetting, msToString, clsx } from '@web/uiUtils';
|
||||||
import type { Entry } from '@trace/har';
|
import type { Entry } from '@trace/har';
|
||||||
import './workbench.css';
|
import './workbench.css';
|
||||||
import { testStatusIcon, testStatusText } from './testUtils';
|
import { testStatusIcon, testStatusText } from './testUtils';
|
||||||
|
|
@ -251,7 +251,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
component: <div className='vbox'>
|
component: <div className='vbox'>
|
||||||
{status && <div className='workbench-run-status'>
|
{status && <div className='workbench-run-status'>
|
||||||
<span className={`codicon ${testStatusIcon(status)}`}></span>
|
<span className={clsx('codicon', testStatusIcon(status))}></span>
|
||||||
<div>{testStatusText(status)}</div>
|
<div>{testStatusText(status)}</div>
|
||||||
<div className='spacer'></div>
|
<div className='spacer'></div>
|
||||||
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
|
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import '@web/common.css';
|
import '@web/common.css';
|
||||||
import { applyTheme } from '@web/theme';
|
import { applyTheme } from '@web/theme';
|
||||||
import '@web/third_party/vscode/codicon.css';
|
import '@web/third_party/vscode/codicon.css';
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { expect, test } from '@playwright/experimental-ct-react';
|
import { expect, test } from '@playwright/experimental-ct-react';
|
||||||
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { expect, test } from '@playwright/experimental-ct-react';
|
import { expect, test } from '@playwright/experimental-ct-react';
|
||||||
import { Expandable } from './expandable';
|
import { Expandable } from './expandable';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './expandable.css';
|
import './expandable.css';
|
||||||
|
import { clsx } from '../uiUtils';
|
||||||
|
|
||||||
export const Expandable: React.FunctionComponent<React.PropsWithChildren<{
|
export const Expandable: React.FunctionComponent<React.PropsWithChildren<{
|
||||||
title: JSX.Element | string,
|
title: JSX.Element | string,
|
||||||
|
|
@ -23,10 +24,10 @@ export const Expandable: React.FunctionComponent<React.PropsWithChildren<{
|
||||||
expanded: boolean,
|
expanded: boolean,
|
||||||
expandOnTitleClick?: boolean,
|
expandOnTitleClick?: boolean,
|
||||||
}>> = ({ title, children, setExpanded, expanded, expandOnTitleClick }) => {
|
}>> = ({ title, children, setExpanded, expanded, expandOnTitleClick }) => {
|
||||||
return <div className={'expandable' + (expanded ? ' expanded' : '')}>
|
return <div className={clsx('expandable', expanded && 'expanded')}>
|
||||||
<div className='expandable-title' onClick={() => expandOnTitleClick && setExpanded(!expanded)}>
|
<div className='expandable-title' onClick={() => expandOnTitleClick && setExpanded(!expanded)}>
|
||||||
<div
|
<div
|
||||||
className={'codicon codicon-' + (expanded ? 'chevron-down' : 'chevron-right')}
|
className={clsx('codicon', expanded ? 'codicon-chevron-down' : 'codicon-chevron-right')}
|
||||||
style={{ cursor: 'pointer', color: 'var(--vscode-foreground)', marginLeft: '5px' }}
|
style={{ cursor: 'pointer', color: 'var(--vscode-foreground)', marginLeft: '5px' }}
|
||||||
onClick={() => !expandOnTitleClick && setExpanded(!expanded)} />
|
onClick={() => !expandOnTitleClick && setExpanded(!expanded)} />
|
||||||
{title}
|
{title}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { expect, test } from '@playwright/experimental-ct-react';
|
import { expect, test } from '@playwright/experimental-ct-react';
|
||||||
import { SplitView } from './splitView';
|
import { SplitView } from './splitView';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMeasure, useSetting } from '../uiUtils';
|
import { clsx, useMeasure, useSetting } from '../uiUtils';
|
||||||
import './splitView.css';
|
import './splitView.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ export const SplitView: React.FC<React.PropsWithChildren<SplitViewProps>> = ({
|
||||||
resizerStyle = { right: resizing ? 0 : size - 4, left: resizing ? 0 : undefined, width: resizing ? 'initial' : 8 };
|
resizerStyle = { right: resizing ? 0 : size - 4, left: resizing ? 0 : undefined, width: resizing ? 'initial' : 8 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={'split-view ' + orientation + (sidebarIsFirst ? ' sidebar-first' : '') } ref={ref}>
|
return <div className={clsx('split-view', orientation, sidebarIsFirst && 'sidebar-first') } ref={ref}>
|
||||||
<div className='split-view-main'>{childrenArray[0]}</div>
|
<div className='split-view-main'>{childrenArray[0]}</div>
|
||||||
{ !sidebarHidden && <div style={{ flexBasis: size }} className='split-view-sidebar'>{childrenArray[1]}</div> }
|
{ !sidebarHidden && <div style={{ flexBasis: size }} className='split-view-sidebar'>{childrenArray[1]}</div> }
|
||||||
{ !sidebarHidden && <div
|
{ !sidebarHidden && <div
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
import './tabbedPane.css';
|
import './tabbedPane.css';
|
||||||
import { Toolbar } from './toolbar';
|
import { Toolbar } from './toolbar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
@ -99,7 +100,7 @@ export const TabbedPaneTab: React.FunctionComponent<{
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
onSelect: (id: string) => void
|
onSelect: (id: string) => void
|
||||||
}> = ({ id, title, count, errorCount, selected, onSelect }) => {
|
}> = ({ id, title, count, errorCount, selected, onSelect }) => {
|
||||||
return <div className={'tabbed-pane-tab ' + (selected ? 'selected' : '')}
|
return <div className={clsx('tabbed-pane-tab', selected && 'selected')}
|
||||||
onClick={() => onSelect(id)}
|
onClick={() => onSelect(id)}
|
||||||
title={title}
|
title={title}
|
||||||
key={id}>
|
key={id}>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
import './toolbar.css';
|
import './toolbar.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
|
@ -31,5 +32,5 @@ export const Toolbar: React.FC<React.PropsWithChildren<ToolbarProps>> = ({
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
return <div className={'toolbar' + (noShadow ? ' no-shadow' : '') + (noMinHeight ? ' no-min-height' : '') + ' ' + (className || '')} onClick={onClick}>{children}</div>;
|
return <div className={clsx('toolbar', noShadow && 'no-shadow', noMinHeight && 'no-min-height', className)} onClick={onClick}>{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { test, expect } from '@playwright/experimental-ct-react';
|
import { test, expect } from '@playwright/experimental-ct-react';
|
||||||
import type { ImageDiff } from './imageDiffView';
|
import type { ImageDiff } from './imageDiffView';
|
||||||
import { ImageDiffView } from './imageDiffView';
|
import { ImageDiffView } from './imageDiffView';
|
||||||
|
|
|
||||||
|
|
@ -191,3 +191,8 @@ export class Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settings = new Settings();
|
export const settings = new Settings();
|
||||||
|
|
||||||
|
// inspired by https://www.npmjs.com/package/clsx
|
||||||
|
export function clsx(...classes: (string | undefined | false)[]) {
|
||||||
|
return classes.filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue