diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index d78b92d938..f262fa19bc 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -658,13 +658,15 @@ class Overlay { private _recordToggle: HTMLElement; private _pickLocatorToggle: HTMLElement; private _assertToggle: HTMLElement; - private _position: { x: number, y: number } = { x: 0, y: 0 }; - private _dragState: { position: { x: number, y: number }, dragStart: { x: number, y: number } } | undefined; + private _offsetX = 0; + private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; constructor(private _recorder: Recorder) { const document = this._recorder.injectedScript.document; this._overlayElement = document.createElement('x-pw-overlay'); + this._overlayElement.style.top = '0'; + this._overlayElement.style.position = 'absolute'; const shadow = this._overlayElement.attachShadow({ mode: 'closed' }); const styleElement = document.createElement('style'); @@ -682,23 +684,7 @@ class Overlay { background-color: hsla(0 0% 100% / .9); font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; display: flex; - flex-direction: column; - margin: 10px; - padding: 3px 0; - border-radius: 17px; - } - - x-pw-drag-handle { - cursor: grab; - padding: 6px 9px; - } - x-pw-drag-handle > div { - height: 1px; - margin-top: 2px; - background: rgb(148 148 148 / 90%); - } - x-pw-drag-handle:active { - cursor: grabbing; + border-radius: 3px; } x-pw-separator { @@ -712,7 +698,7 @@ class Overlay { height: 28px; width: 28px; margin: 2px 4px; - border-radius: 50%; + border-radius: 3px; } x-pw-tool-item:not(.disabled):hover { background-color: hsl(0, 0%, 86%); @@ -738,7 +724,28 @@ class Overlay { x-pw-tool-item.record.active > div { background-color: #a1260d; } - + x-pw-tool-gripper { + height: 28px; + width: 24px; + margin: 2px 0; + cursor: grab; + } + x-pw-tool-gripper:active { + cursor: grabbing; + } + x-pw-tool-gripper > div { + width: 100%; + height: 100%; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 16px; + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); + background-color: #555555; + } x-pw-tool-item.record > div { /* codicon: circle-large-filled */ -webkit-mask-image: url("data:image/svg+xml;utf8,"); @@ -754,17 +761,19 @@ class Overlay { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } - x-pw-tool-item.close > div { - /* codicon: close */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - } `; shadow.appendChild(styleElement); const toolsListElement = document.createElement('x-pw-tools-list'); shadow.appendChild(toolsListElement); + const dragHandle = document.createElement('x-pw-tool-gripper'); + dragHandle.addEventListener('mousedown', event => { + this._dragState = { offsetX: this._offsetX, dragStart: { x: event.clientX, y: 0 } }; + }); + dragHandle.appendChild(document.createElement('div')); + toolsListElement.appendChild(dragHandle); + this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._recordToggle.title = 'Record'; this._recordToggle.classList.add('record'); @@ -774,15 +783,6 @@ class Overlay { }); toolsListElement.appendChild(this._recordToggle); - const dragHandle = document.createElement('x-pw-drag-handle'); - dragHandle.addEventListener('mousedown', event => { - this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } }; - }); - dragHandle.append(document.createElement('div')); - dragHandle.append(document.createElement('div')); - dragHandle.append(document.createElement('div')); - toolsListElement.appendChild(dragHandle); - this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._pickLocatorToggle.title = 'Pick locator'; this._pickLocatorToggle.classList.add('pick-locator'); @@ -809,16 +809,6 @@ class Overlay { }); toolsListElement.appendChild(this._assertToggle); - const closeButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - closeButton.title = 'Hide this overlay'; - closeButton.classList.add('close'); - closeButton.appendChild(this._recorder.injectedScript.document.createElement('div')); - closeButton.addEventListener('click', () => { - this._overlayElement.style.display = 'none'; - this._recorder.delegate.setOverlayState?.({ position: this._position, visible: false }); - }); - toolsListElement.appendChild(closeButton); - this._updateVisualPosition(); } @@ -836,16 +826,14 @@ class Overlay { this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); - if (this._position.x !== state.overlay.position.x || this._position.y !== state.overlay.position.y) { - this._position = state.overlay.position; + if (this._offsetX !== state.overlay.offsetX) { + this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); } - this._overlayElement.style.display = state.overlay.visible ? 'block' : 'none'; } private _updateVisualPosition() { - this._overlayElement.style.left = this._position.x + 'px'; - this._overlayElement.style.top = this._position.y + 'px'; + this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px'; } onMouseMove(event: MouseEvent) { @@ -854,14 +842,11 @@ class Overlay { return false; } if (this._dragState) { - this._position = { - x: this._dragState.position.x + event.clientX - this._dragState.dragStart.x, - y: this._dragState.position.y + event.clientY - this._dragState.dragStart.y, - }; - this._position.x = Math.max(0, Math.min(this._recorder.injectedScript.window.innerWidth - this._measure.width, this._position.x)); - this._position.y = Math.max(0, Math.min(this._recorder.injectedScript.window.innerHeight - this._measure.height, this._position.y)); + this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x; + this._offsetX = Math.min(this._recorder.injectedScript.window.innerWidth / 2 - 10 - this._measure.width, this._offsetX); + this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX); this._updateVisualPosition(); - this._recorder.delegate.setOverlayState?.({ position: this._position, visible: true }); + this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX }); consumeEvent(event); return true; } @@ -869,6 +854,14 @@ class Overlay { } onMouseUp(event: MouseEvent) { + if (this._dragState) { + consumeEvent(event); + return true; + } + return false; + } + + onClick(event: MouseEvent) { if (this._dragState) { this._dragState = undefined; consumeEvent(event); @@ -887,7 +880,7 @@ export class Recorder { private _highlight: Highlight; private _overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; - state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { position: { x: 0, y: 0 }, visible: true } }; + state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; readonly document: Document; delegate: RecorderDelegate = {}; @@ -991,6 +984,8 @@ export class Recorder { private _onClick(event: MouseEvent) { if (!event.isTrusted) return; + if (this._overlay?.onClick(event)) + return; if (this._ignoreOverlayEvent(event)) return; this._currentTool.onClick?.(event); diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 553d12fcc7..f5329072f9 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -55,7 +55,7 @@ export class Recorder implements InstrumentationListener { private _context: BrowserContext; private _mode: Mode; private _highlightedSelector = ''; - private _overlayState: OverlayState = { position: { x: 0, y: 0 }, visible: true }; + private _overlayState: OverlayState = { offsetX: 0 }; private _recorderApp: IRecorderApp | null = null; private _currentCallsMetadata = new Map(); private _recorderSources: Source[] = []; @@ -101,7 +101,7 @@ export class Recorder implements InstrumentationListener { if (isUnderTest()) { // Most of our tests put elements at the top left, so get out of the way. - this._overlayState.position = { x: 350, y: 350 }; + this._overlayState.offsetX = 200; } } @@ -123,12 +123,6 @@ export class Recorder implements InstrumentationListener { this.setMode(data.params.mode); return; } - if (data.event === 'setOverlayVisible') { - this._overlayState.visible = data.params.visible; - this._recorderApp?.setOverlayVisible(this._overlayState.visible); - this._refreshOverlay(); - return; - } if (data.event === 'selectorUpdated') { this.setHighlightedSelector(this._currentLanguage, data.params.selector); return; @@ -219,7 +213,6 @@ export class Recorder implements InstrumentationListener { if (frame.parentFrame()) return; this._overlayState = state; - this._recorderApp?.setOverlayVisible(state.visible); }); await this._context.exposeBinding('__pw_resume', false, () => { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index de108c8fc4..6f56db67b7 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -46,7 +46,6 @@ export interface IRecorderApp extends EventEmitter { close(): Promise; setPaused(paused: boolean): Promise; setMode(mode: Mode): Promise; - setOverlayVisible(visible: boolean): Promise; setFileIfNeeded(file: string): Promise; setSelector(selector: string, userGesture?: boolean): Promise; updateCallLogs(callLogs: CallLog[]): Promise; @@ -57,7 +56,6 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async close(): Promise {} async setPaused(paused: boolean): Promise {} async setMode(mode: Mode): Promise {} - async setOverlayVisible(visible: boolean): Promise {} async setFileIfNeeded(file: string): Promise {} async setSelector(selector: string, userGesture?: boolean): Promise {} async updateCallLogs(callLogs: CallLog[]): Promise {} @@ -147,12 +145,6 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { }).toString(), { isFunction: true }, mode).catch(() => {}); } - async setOverlayVisible(visible: boolean): Promise { - await this._page.mainFrame().evaluateExpression(((visible: boolean) => { - window.playwrightSetOverlayVisible(visible); - }).toString(), { isFunction: true }, visible).catch(() => {}); - } - async setFileIfNeeded(file: string): Promise { await this._page.mainFrame().evaluateExpression(((file: string) => { window.playwrightSetFileIfNeeded(file); diff --git a/packages/recorder/src/main.tsx b/packages/recorder/src/main.tsx index be5791eb15..5980716d1e 100644 --- a/packages/recorder/src/main.tsx +++ b/packages/recorder/src/main.tsx @@ -25,12 +25,10 @@ export const Main: React.FC = ({ const [paused, setPaused] = React.useState(false); const [log, setLog] = React.useState(new Map()); const [mode, setMode] = React.useState('none'); - const [overlayVisible, setOverlayVisible] = React.useState(true); window.playwrightSetMode = setMode; window.playwrightSetSources = setSources; window.playwrightSetPaused = setPaused; - window.playwrightSetOverlayVisible = setOverlayVisible; window.playwrightUpdateLogs = callLogs => { const newLog = new Map(log); for (const callLog of callLogs) { @@ -41,5 +39,5 @@ export const Main: React.FC = ({ }; window.playwrightSourcesEchoForTest = sources; - return ; + return ; }; diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 65d5628b54..6a1c6eb325 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -40,7 +40,6 @@ export interface RecorderProps { paused: boolean, log: Map, mode: Mode, - overlayVisible: boolean, } export const Recorder: React.FC = ({ @@ -48,7 +47,6 @@ export const Recorder: React.FC = ({ paused, log, mode, - overlayVisible, }) => { const [fileId, setFileId] = React.useState(); const [selectedTab, setSelectedTab] = React.useState('log'); @@ -156,9 +154,6 @@ export const Recorder: React.FC = ({ window.dispatch({ event: 'clear' }); }}> toggleTheme()}> - { - window.dispatch({ event: 'setOverlayVisible', params: { visible: !overlayVisible } }); - }}> diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index d8ac13240e..0d4523d09f 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -21,13 +21,12 @@ export type Point = { x: number, y: number }; export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; export type EventData = { - event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged' | 'setOverlayVisible'; + event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; params: any; }; export type OverlayState = { - position: Point; - visible: boolean; + offsetX: number; }; export type UIState = { diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index 793fb93908..a292fd6785 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -242,7 +242,7 @@ export const InspectModeController: React.FunctionComponent<{ actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined, language: sdkLanguage, testIdAttributeName, - overlay: { position: { x: 0, y: 0 }, visible: false }, + overlay: { offsetX: 0 }, }, { async setSelector(selector: string) { setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */));