chore: allow toggling recorder/traceviewer color modes (#18718)

Fixes: https://github.com/microsoft/playwright/issues/18700
This commit is contained in:
Pavel Feldman 2022-11-10 17:20:09 -08:00 committed by GitHub
parent dfb4ad388a
commit d5eb74fa5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 607 additions and 552 deletions

View File

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

View File

@ -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/')) {

View File

@ -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()) {

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff