mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 20:03:03 +03:00
chore: allow toggling recorder/traceviewer color modes (#18718)
Fixes: https://github.com/microsoft/playwright/issues/18700
This commit is contained in:
parent
dfb4ad388a
commit
d5eb74fa5d
@ -15,7 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import type { Page } from '../page';
|
import type { Page } from '../page';
|
||||||
|
import { registryDirectory } from '../registry';
|
||||||
import type { CRPage } from './crPage';
|
import type { CRPage } from './crPage';
|
||||||
|
|
||||||
export async function installAppIcon(page: Page) {
|
export async function installAppIcon(page: Page) {
|
||||||
@ -25,3 +27,25 @@ export async function installAppIcon(page: Page) {
|
|||||||
image: icon.toString('base64')
|
image: icon.toString('base64')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function syncLocalStorageWithSettings(page: Page, appName: string) {
|
||||||
|
const settingsFile = path.join(registryDirectory, '.settings', `${appName}.json`);
|
||||||
|
await page.exposeBinding('saveSettings', false, (_, settings: any) => {
|
||||||
|
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
||||||
|
fs.writeFileSync(settingsFile, settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = await fs.promises.readFile(settingsFile, 'utf-8').catch(() => ('{}'));
|
||||||
|
await page.addInitScript(`(${String((settings: any) => {
|
||||||
|
Object.entries(settings).map(([k, v]) => localStorage[k] = v);
|
||||||
|
|
||||||
|
let lastValue = JSON.stringify(localStorage);
|
||||||
|
setInterval(() => {
|
||||||
|
const value = JSON.stringify(localStorage);
|
||||||
|
if (value !== lastValue) {
|
||||||
|
lastValue = value;
|
||||||
|
window.saveSettings(value);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
})})(${settings})`);
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ import { serverSideCallMetadata } from '../instrumentation';
|
|||||||
import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes';
|
import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes';
|
||||||
import { isUnderTest } from '../../utils';
|
import { isUnderTest } from '../../utils';
|
||||||
import { mime } from '../../utilsBundle';
|
import { mime } from '../../utilsBundle';
|
||||||
import { installAppIcon } from '../chromium/crApp';
|
import { installAppIcon, syncLocalStorageWithSettings } from '../chromium/crApp';
|
||||||
import { findChromiumChannel } from '../registry';
|
import { findChromiumChannel } from '../registry';
|
||||||
import type { Recorder } from '../recorder';
|
import type { Recorder } from '../recorder';
|
||||||
import type { BrowserContext } from '../browserContext';
|
import type { BrowserContext } from '../browserContext';
|
||||||
@ -37,6 +37,7 @@ declare global {
|
|||||||
playwrightSetSelector: (selector: string, focus?: boolean) => void;
|
playwrightSetSelector: (selector: string, focus?: boolean) => void;
|
||||||
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
||||||
dispatch(data: EventData): Promise<void>;
|
dispatch(data: EventData): Promise<void>;
|
||||||
|
saveSettings(data: any): Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
|||||||
|
|
||||||
private async _init() {
|
private async _init() {
|
||||||
await installAppIcon(this._page);
|
await installAppIcon(this._page);
|
||||||
|
await syncLocalStorageWithSettings(this._page, 'recorder');
|
||||||
|
|
||||||
await this._page._setServerRequestInterceptor(async route => {
|
await this._page._setServerRequestInterceptor(async route => {
|
||||||
if (route.request().url().startsWith('https://playwright/')) {
|
if (route.request().url().startsWith('https://playwright/')) {
|
||||||
|
@ -21,7 +21,7 @@ import { HttpServer } from '../../../utils/httpServer';
|
|||||||
import { findChromiumChannel } from '../../registry';
|
import { findChromiumChannel } from '../../registry';
|
||||||
import { isUnderTest } from '../../../utils';
|
import { isUnderTest } from '../../../utils';
|
||||||
import type { BrowserContext } from '../../browserContext';
|
import type { BrowserContext } from '../../browserContext';
|
||||||
import { installAppIcon } from '../../chromium/crApp';
|
import { installAppIcon, syncLocalStorageWithSettings } from '../../chromium/crApp';
|
||||||
import { serverSideCallMetadata } from '../../instrumentation';
|
import { serverSideCallMetadata } from '../../instrumentation';
|
||||||
import { createPlaywright } from '../../playwright';
|
import { createPlaywright } from '../../playwright';
|
||||||
import { ProgressController } from '../../progress';
|
import { ProgressController } from '../../progress';
|
||||||
@ -81,7 +81,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
|||||||
|
|
||||||
if (traceViewerBrowser === 'chromium')
|
if (traceViewerBrowser === 'chromium')
|
||||||
await installAppIcon(page);
|
await installAppIcon(page);
|
||||||
|
await syncLocalStorageWithSettings(page, 'traceviewer');
|
||||||
|
|
||||||
const params = traceUrls.map(t => `trace=${t}`);
|
const params = traceUrls.map(t => `trace=${t}`);
|
||||||
if (isUnderTest()) {
|
if (isUnderTest()) {
|
||||||
|
@ -24,6 +24,7 @@ import * as React from 'react';
|
|||||||
import { CallLogView } from './callLog';
|
import { CallLogView } from './callLog';
|
||||||
import './recorder.css';
|
import './recorder.css';
|
||||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
|
import { toggleTheme } from '@web/theme';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -129,6 +130,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
|||||||
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
|
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
|
||||||
window.dispatch({ event: 'clear' });
|
window.dispatch({ event: 'clear' });
|
||||||
}}></ToolbarButton>
|
}}></ToolbarButton>
|
||||||
|
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<SplitView sidebarSize={200} sidebarHidden={mode === 'recording'}>
|
<SplitView sidebarSize={200} sidebarHidden={mode === 'recording'}>
|
||||||
<SourceView text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></SourceView>
|
<SourceView text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></SourceView>
|
||||||
|
@ -85,6 +85,11 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workbench .header .toolbar-button {
|
||||||
|
margin: 12px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.workbench tab-content {
|
.workbench tab-content {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
contain: size;
|
contain: size;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import type { ActionTraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent } from '@trace/trace';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { msToString } from '@web/uiUtils';
|
||||||
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { ContextEntry } from '../entries';
|
import type { ContextEntry } from '../entries';
|
||||||
import { ActionList } from './actionList';
|
import { ActionList } from './actionList';
|
||||||
@ -30,6 +31,7 @@ import { SourceTab } from './sourceTab';
|
|||||||
import { TabbedPane } from './tabbedPane';
|
import { TabbedPane } from './tabbedPane';
|
||||||
import { Timeline } from './timeline';
|
import { Timeline } from './timeline';
|
||||||
import './workbench.css';
|
import './workbench.css';
|
||||||
|
import { toggleTheme } from '@web/theme';
|
||||||
|
|
||||||
export const Workbench: React.FunctionComponent<{
|
export const Workbench: React.FunctionComponent<{
|
||||||
}> = () => {
|
}> = () => {
|
||||||
@ -156,6 +158,7 @@ export const Workbench: React.FunctionComponent<{
|
|||||||
<div className='product'>Playwright</div>
|
<div className='product'>Playwright</div>
|
||||||
{model.title && <div className='title'>{model.title}</div>}
|
{model.title && <div className='title'>{model.title}</div>}
|
||||||
<div className='spacer'></div>
|
<div className='spacer'></div>
|
||||||
|
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ paddingLeft: '20px', flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
|
<div style={{ paddingLeft: '20px', flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
|
||||||
<Timeline
|
<Timeline
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
--red: #F44336;
|
--red: #F44336;
|
||||||
--green: #367c39;
|
--green: #367c39;
|
||||||
--purple: #9C27B0;
|
--purple: #9C27B0;
|
||||||
@ -29,14 +33,12 @@
|
|||||||
--box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
--box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(prefers-color-scheme: dark) {
|
body.dark-mode {
|
||||||
:root {
|
--green: #28d12f;
|
||||||
--green: #28d12f;
|
--yellow: #ff9207;
|
||||||
--yellow: #ff9207;
|
--purple: #dc12ff;
|
||||||
--purple: #dc12ff;
|
--blue: #4dafff;
|
||||||
--blue: #4dafff;
|
--orange: #ff9800;
|
||||||
--orange: #ff9800;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
|
@ -25,4 +25,23 @@ export function applyTheme() {
|
|||||||
document!.defaultView!.addEventListener('blur', event => {
|
document!.defaultView!.addEventListener('blur', event => {
|
||||||
document.body.classList.add('inactive');
|
document.body.classList.add('inactive');
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
const currentTheme = localStorage.getItem('theme');
|
||||||
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
if (currentTheme === 'dark-mode' || prefersDarkScheme.matches)
|
||||||
|
document.body.classList.add('dark-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleTheme() {
|
||||||
|
const oldTheme = localStorage.getItem('theme');
|
||||||
|
let newTheme: string;
|
||||||
|
if (oldTheme === 'dark-mode')
|
||||||
|
newTheme = 'light-mode';
|
||||||
|
else
|
||||||
|
newTheme = 'dark-mode';
|
||||||
|
|
||||||
|
if (oldTheme)
|
||||||
|
document.body.classList.remove(oldTheme);
|
||||||
|
document.body.classList.add(newTheme);
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
}
|
}
|
||||||
|
1080
packages/web/src/third_party/vscode/colors.css
vendored
1080
packages/web/src/third_party/vscode/colors.css
vendored
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user