From 9a94919052affb620b3e3a31ff77b6dc2c09e6fb Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Tue, 19 Dec 2023 13:00:05 +0000 Subject: [PATCH 1/3] feat: keyboard placeholder --- .../__snapshots__/transform.test.ts.snap | 181 +++++++++++------- ee/frontend/mobile-replay/index.ts | 3 +- ee/frontend/mobile-replay/mobile.types.ts | 39 +++- .../schema/mobile/rr-mobile-schema.json | 99 ++++++++-- ee/frontend/mobile-replay/transform.test.ts | 20 ++ ee/frontend/mobile-replay/transformers.ts | 101 +++++++++- ee/frontend/mobile-replay/wireframeStyle.ts | 28 +-- 7 files changed, 366 insertions(+), 105 deletions(-) diff --git a/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap index 195c8f7da943f..a343dc74144d0 100644 --- a/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap +++ b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap @@ -612,6 +612,25 @@ exports[`replay/transform transform inputs buttons with nested elements 1`] = ` } `; +exports[`replay/transform transform inputs closed keyboard custom event 1`] = ` +{ + "data": { + "adds": [], + "attributes": [], + "removes": [ + { + "id": 6, + "parentId": 5, + }, + ], + "source": 0, + "texts": [], + }, + "timestamp": 1, + "type": 3, +} +`; + exports[`replay/transform transform inputs input - $inputType - hello 1`] = ` { "data": { @@ -648,7 +667,7 @@ exports[`replay/transform transform inputs input - $inputType - hello 1`] = ` { "attributes": {}, "childNodes": [], - "id": 222, + "id": 223, "tagName": "div", "type": 2, }, @@ -715,7 +734,7 @@ exports[`replay/transform transform inputs input - button - click me 1`] = ` }, "childNodes": [ { - "id": 214, + "id": 215, "textContent": "click me", "type": 3, }, @@ -725,7 +744,7 @@ exports[`replay/transform transform inputs input - button - click me 1`] = ` "type": 2, }, ], - "id": 213, + "id": 214, "tagName": "div", "type": 2, }, @@ -802,17 +821,17 @@ exports[`replay/transform transform inputs input - checkbox - $value 1`] = ` "type": 2, }, { - "id": 182, + "id": 183, "textContent": "first", "type": 3, }, ], - "id": 183, + "id": 184, "tagName": "label", "type": 2, }, ], - "id": 181, + "id": 182, "tagName": "div", "type": 2, }, @@ -888,17 +907,17 @@ exports[`replay/transform transform inputs input - checkbox - $value 2`] = ` "type": 2, }, { - "id": 185, + "id": 186, "textContent": "second", "type": 3, }, ], - "id": 186, + "id": 187, "tagName": "label", "type": 2, }, ], - "id": 184, + "id": 185, "tagName": "div", "type": 2, }, @@ -976,17 +995,17 @@ exports[`replay/transform transform inputs input - checkbox - $value 3`] = ` "type": 2, }, { - "id": 188, + "id": 189, "textContent": "third", "type": 3, }, ], - "id": 189, + "id": 190, "tagName": "label", "type": 2, }, ], - "id": 187, + "id": 188, "tagName": "div", "type": 2, }, @@ -1058,7 +1077,7 @@ exports[`replay/transform transform inputs input - checkbox - $value 4`] = ` "type": 2, }, ], - "id": 190, + "id": 191, "tagName": "div", "type": 2, }, @@ -1130,7 +1149,7 @@ exports[`replay/transform transform inputs input - email - $value 1`] = ` "type": 2, }, ], - "id": 174, + "id": 175, "tagName": "div", "type": 2, }, @@ -1202,7 +1221,7 @@ exports[`replay/transform transform inputs input - number - $value 1`] = ` "type": 2, }, ], - "id": 175, + "id": 176, "tagName": "div", "type": 2, }, @@ -1274,7 +1293,7 @@ exports[`replay/transform transform inputs input - password - $value 1`] = ` "type": 2, }, ], - "id": 173, + "id": 174, "tagName": "div", "type": 2, }, @@ -1350,17 +1369,17 @@ exports[`replay/transform transform inputs input - progress - $value 1`] = ` }, "childNodes": [ { - "id": 225, + "id": 226, "textContent": "@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }", "type": 3, }, ], - "id": 224, + "id": 225, "tagName": "style", "type": 2, }, ], - "id": 226, + "id": 227, "tagName": "div", "type": 2, }, @@ -1370,7 +1389,7 @@ exports[`replay/transform transform inputs input - progress - $value 1`] = ` "type": 2, }, ], - "id": 223, + "id": 224, "tagName": "div", "type": 2, }, @@ -1443,7 +1462,7 @@ exports[`replay/transform transform inputs input - progress - $value 2`] = ` "type": 2, }, ], - "id": 227, + "id": 228, "tagName": "div", "type": 2, }, @@ -1516,7 +1535,7 @@ exports[`replay/transform transform inputs input - progress - 0.75 1`] = ` "type": 2, }, ], - "id": 228, + "id": 229, "tagName": "div", "type": 2, }, @@ -1589,7 +1608,7 @@ exports[`replay/transform transform inputs input - progress - 0.75 2`] = ` "type": 2, }, ], - "id": 229, + "id": 230, "tagName": "div", "type": 2, }, @@ -1661,7 +1680,7 @@ exports[`replay/transform transform inputs input - search - $value 1`] = ` "type": 2, }, ], - "id": 176, + "id": 177, "tagName": "div", "type": 2, }, @@ -1734,12 +1753,12 @@ exports[`replay/transform transform inputs input - select - hello 1`] = ` }, "childNodes": [ { - "id": 219, + "id": 220, "textContent": "hello", "type": 3, }, ], - "id": 218, + "id": 219, "tagName": "option", "type": 2, }, @@ -1747,12 +1766,12 @@ exports[`replay/transform transform inputs input - select - hello 1`] = ` "attributes": {}, "childNodes": [ { - "id": 221, + "id": 222, "textContent": "world", "type": 3, }, ], - "id": 220, + "id": 221, "tagName": "option", "type": 2, }, @@ -1762,7 +1781,7 @@ exports[`replay/transform transform inputs input - select - hello 1`] = ` "type": 2, }, ], - "id": 217, + "id": 218, "tagName": "div", "type": 2, }, @@ -1835,7 +1854,7 @@ exports[`replay/transform transform inputs input - tel - $value 1`] = ` "type": 2, }, ], - "id": 177, + "id": 178, "tagName": "div", "type": 2, }, @@ -1907,7 +1926,7 @@ exports[`replay/transform transform inputs input - text - $value 1`] = ` "type": 2, }, ], - "id": 172, + "id": 173, "tagName": "div", "type": 2, }, @@ -1979,7 +1998,7 @@ exports[`replay/transform transform inputs input - text - hello 1`] = ` "type": 2, }, ], - "id": 171, + "id": 172, "tagName": "div", "type": 2, }, @@ -2051,7 +2070,7 @@ exports[`replay/transform transform inputs input - textArea - $value 1`] = ` "type": 2, }, ], - "id": 216, + "id": 217, "tagName": "div", "type": 2, }, @@ -2123,7 +2142,7 @@ exports[`replay/transform transform inputs input - textArea - hello 1`] = ` "type": 2, }, ], - "id": 215, + "id": 216, "tagName": "div", "type": 2, }, @@ -2189,7 +2208,7 @@ exports[`replay/transform transform inputs input - toggle - $value 1`] = ` }, "childNodes": [ { - "id": 195, + "id": 196, "textContent": "first", "type": 3, }, @@ -2209,7 +2228,7 @@ exports[`replay/transform transform inputs input - toggle - $value 1`] = ` "style": "position:absolute;top:33%;left:5%;display:inline-block;width:75%;height:33%;background-color:#1d4aff;opacity: 0.2;border-radius:7.5%;", }, "childNodes": [], - "id": 193, + "id": 194, "tagName": "div", "type": 2, }, @@ -2219,12 +2238,12 @@ exports[`replay/transform transform inputs input - toggle - $value 1`] = ` "style": "position:absolute;top:1.5%;right:5%;display:flex;align-items:center;justify-content:center;width:40%;height:75%;cursor:inherit;background-color:#1d4aff;border:2px solid #1d4aff;border-radius:50%;", }, "childNodes": [], - "id": 194, + "id": 195, "tagName": "div", "type": 2, }, ], - "id": 192, + "id": 193, "tagName": "div", "type": 2, }, @@ -2234,12 +2253,12 @@ exports[`replay/transform transform inputs input - toggle - $value 1`] = ` "type": 2, }, ], - "id": 196, + "id": 197, "tagName": "label", "type": 2, }, ], - "id": 191, + "id": 192, "tagName": "div", "type": 2, }, @@ -2305,7 +2324,7 @@ exports[`replay/transform transform inputs input - toggle - $value 2`] = ` }, "childNodes": [ { - "id": 201, + "id": 202, "textContent": "second", "type": 3, }, @@ -2325,7 +2344,7 @@ exports[`replay/transform transform inputs input - toggle - $value 2`] = ` "style": "position:absolute;top:33%;left:5%;display:inline-block;width:75%;height:33%;background-color:#f3f4ef;opacity: 0.2;border-radius:7.5%;", }, "childNodes": [], - "id": 199, + "id": 200, "tagName": "div", "type": 2, }, @@ -2335,12 +2354,12 @@ exports[`replay/transform transform inputs input - toggle - $value 2`] = ` "style": "position:absolute;top:1.5%;left:5%;display:flex;align-items:center;justify-content:center;width:40%;height:75%;cursor:inherit;background-color:#f3f4ef;border:2px solid #f3f4ef;border-radius:50%;", }, "childNodes": [], - "id": 200, + "id": 201, "tagName": "div", "type": 2, }, ], - "id": 198, + "id": 199, "tagName": "div", "type": 2, }, @@ -2350,12 +2369,12 @@ exports[`replay/transform transform inputs input - toggle - $value 2`] = ` "type": 2, }, ], - "id": 202, + "id": 203, "tagName": "label", "type": 2, }, ], - "id": 197, + "id": 198, "tagName": "div", "type": 2, }, @@ -2421,7 +2440,7 @@ exports[`replay/transform transform inputs input - toggle - $value 3`] = ` }, "childNodes": [ { - "id": 207, + "id": 208, "textContent": "third", "type": 3, }, @@ -2441,7 +2460,7 @@ exports[`replay/transform transform inputs input - toggle - $value 3`] = ` "style": "position:absolute;top:33%;left:5%;display:inline-block;width:75%;height:33%;background-color:#1d4aff;opacity: 0.2;border-radius:7.5%;", }, "childNodes": [], - "id": 205, + "id": 206, "tagName": "div", "type": 2, }, @@ -2451,12 +2470,12 @@ exports[`replay/transform transform inputs input - toggle - $value 3`] = ` "style": "position:absolute;top:1.5%;right:5%;display:flex;align-items:center;justify-content:center;width:40%;height:75%;cursor:inherit;background-color:#1d4aff;border:2px solid #1d4aff;border-radius:50%;", }, "childNodes": [], - "id": 206, + "id": 207, "tagName": "div", "type": 2, }, ], - "id": 204, + "id": 205, "tagName": "div", "type": 2, }, @@ -2466,12 +2485,12 @@ exports[`replay/transform transform inputs input - toggle - $value 3`] = ` "type": 2, }, ], - "id": 208, + "id": 209, "tagName": "label", "type": 2, }, ], - "id": 203, + "id": 204, "tagName": "div", "type": 2, }, @@ -2547,7 +2566,7 @@ exports[`replay/transform transform inputs input - toggle - $value 4`] = ` "style": "position:absolute;top:33%;left:5%;display:inline-block;width:75%;height:33%;background-color:#1d4aff;opacity: 0.2;border-radius:7.5%;", }, "childNodes": [], - "id": 211, + "id": 212, "tagName": "div", "type": 2, }, @@ -2557,12 +2576,12 @@ exports[`replay/transform transform inputs input - toggle - $value 4`] = ` "style": "position:absolute;top:1.5%;right:5%;display:flex;align-items:center;justify-content:center;width:40%;height:75%;cursor:inherit;background-color:#1d4aff;border:2px solid #1d4aff;border-radius:50%;", }, "childNodes": [], - "id": 212, + "id": 213, "tagName": "div", "type": 2, }, ], - "id": 210, + "id": 211, "tagName": "div", "type": 2, }, @@ -2572,7 +2591,7 @@ exports[`replay/transform transform inputs input - toggle - $value 4`] = ` "type": 2, }, ], - "id": 209, + "id": 210, "tagName": "div", "type": 2, }, @@ -2644,7 +2663,7 @@ exports[`replay/transform transform inputs input - url - https://example.io 1`] "type": 2, }, ], - "id": 178, + "id": 179, "tagName": "div", "type": 2, }, @@ -2668,6 +2687,40 @@ exports[`replay/transform transform inputs input - url - https://example.io 1`] } `; +exports[`replay/transform transform inputs open keyboard custom event 1`] = ` +{ + "data": { + "adds": [ + { + "nextId": null, + "node": { + "attributes": { + "style": "color: #35373e;background-color: #f3f4ef;width: 100vw;height: 150px;position: fixed;left: 0px;top: 0px;align-items: center;justify-content: center;display: flex;", + }, + "childNodes": [ + { + "id": 170, + "textContent": "keyboard", + "type": 3, + }, + ], + "id": 6, + "tagName": "div", + "type": 2, + }, + "parentId": 5, + }, + ], + "attributes": [], + "removes": [], + "source": 0, + "texts": [], + }, + "timestamp": 1, + "type": 3, +} +`; + exports[`replay/transform transform inputs placeholder - $inputType - $value 1`] = ` { "data": { @@ -2710,7 +2763,7 @@ exports[`replay/transform transform inputs placeholder - $inputType - $value 1`] }, "childNodes": [ { - "id": 231, + "id": 232, "textContent": "hello", "type": 3, }, @@ -2720,7 +2773,7 @@ exports[`replay/transform transform inputs placeholder - $inputType - $value 1`] "type": 2, }, ], - "id": 230, + "id": 231, "tagName": "div", "type": 2, }, @@ -3305,7 +3358,7 @@ exports[`replay/transform transform inputs radio group - $inputType - $value 1`] { "attributes": {}, "childNodes": [], - "id": 180, + "id": 181, "tagName": "div", "type": 2, }, @@ -3375,7 +3428,7 @@ exports[`replay/transform transform inputs radio_group - $inputType - $value 1`] "type": 2, }, ], - "id": 179, + "id": 180, "tagName": "div", "type": 2, }, @@ -3445,7 +3498,7 @@ exports[`replay/transform transform inputs radio_group 1`] = ` "type": 2, }, ], - "id": 170, + "id": 171, "tagName": "div", "type": 2, }, @@ -3511,7 +3564,7 @@ exports[`replay/transform transform inputs web_view - $inputType - $value 1`] = }, "childNodes": [ { - "id": 233, + "id": 234, "textContent": "web_view", "type": 3, }, @@ -3521,7 +3574,7 @@ exports[`replay/transform transform inputs web_view - $inputType - $value 1`] = "type": 2, }, ], - "id": 232, + "id": 233, "tagName": "div", "type": 2, }, diff --git a/ee/frontend/mobile-replay/index.ts b/ee/frontend/mobile-replay/index.ts index 85f2db138652f..b696832788379 100644 --- a/ee/frontend/mobile-replay/index.ts +++ b/ee/frontend/mobile-replay/index.ts @@ -5,7 +5,7 @@ import Ajv, { ErrorObject } from 'ajv' import { mobileEventWithTime } from './mobile.types' import mobileSchema from './schema/mobile/rr-mobile-schema.json' import webSchema from './schema/web/rr-web-schema.json' -import { makeFullEvent, makeIncrementalEvent, makeMetaEvent } from './transformers' +import { makeCustomEvent, makeFullEvent, makeIncrementalEvent, makeMetaEvent } from './transformers' const ajv = new Ajv({ allowUnionTypes: true, @@ -15,6 +15,7 @@ const transformers: Record eventWithTime> = { 2: makeFullEvent, 3: makeIncrementalEvent, 4: makeMetaEvent, + 5: makeCustomEvent, } const mobileSchemaValidator = ajv.compile(mobileSchema) diff --git a/ee/frontend/mobile-replay/mobile.types.ts b/ee/frontend/mobile-replay/mobile.types.ts index 959791821c5c1..9349eacd5ea18 100644 --- a/ee/frontend/mobile-replay/mobile.types.ts +++ b/ee/frontend/mobile-replay/mobile.types.ts @@ -118,13 +118,16 @@ type wireframeBase = { /** * @description x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0 */ - x: number - y: number + x?: number + y?: number /* - * @description width and height are the dimensions of the element, the only accepted units is pixels. You can omit the unit. + * @description the width dimension of the element, the only accepted units is pixels. You can omit the unit. If not received it is not set */ - width: number - height: number + width?: number + /* + * @description the height dimension of the element, the only accepted units is pixels. You can omit the unit. If not received it is not set + */ + height?: number childWireframes?: wireframe[] type: MobileNodeType style?: MobileStyles @@ -289,7 +292,31 @@ export type metaEvent = { } } -export type mobileEvent = fullSnapshotEvent | metaEvent | customEvent | incrementalSnapshotEvent +// this is a custom event _but_ rrweb only types tag as string, and we want to be more specific +export type keyboardEvent = { + type: EventType.Custom + data: { + tag: 'keyboard' + payload: + | { + open: true + styles?: MobileStyles + /** + * @description x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting `bottom: 0` + */ + x?: number + y?: number + /* + * @description width and height are the dimensions of the element, the only accepted units is pixels. You can omit the unit. A keyboard always has height, and defaults to width of the viewport + */ + height: number + width?: number + } + | { open: false } + } +} + +export type mobileEvent = fullSnapshotEvent | metaEvent | customEvent | incrementalSnapshotEvent | keyboardEvent export type mobileEventWithTime = mobileEvent & { timestamp: number diff --git a/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json index 63151e3a48cad..d0bbe5ec34122 100644 --- a/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json +++ b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json @@ -119,6 +119,75 @@ }, "required": ["data", "timestamp", "type"], "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "data": { + "additionalProperties": false, + "properties": { + "payload": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "open": { + "const": true, + "type": "boolean" + }, + "styles": { + "$ref": "#/definitions/MobileStyles" + }, + "width": { + "type": "number" + }, + "x": { + "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting `bottom: 0`", + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["open", "height"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "open": { + "const": false, + "type": "boolean" + } + }, + "required": ["open"], + "type": "object" + } + ] + }, + "tag": { + "const": "keyboard", + "type": "string" + } + }, + "required": ["tag", "payload"], + "type": "object" + }, + "delay": { + "type": "number" + }, + "timestamp": { + "type": "number" + }, + "type": { + "$ref": "#/definitions/EventType.Custom" + } + }, + "required": ["data", "timestamp", "type"], + "type": "object" } ], "definitions": { @@ -258,7 +327,7 @@ "type": "number" } }, - "required": ["disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["disabled", "id", "inputType", "type"], "type": "object" }, "wireframeCheckBox": { @@ -308,7 +377,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["checked", "disabled", "id", "inputType", "type"], "type": "object" }, "wireframeDiv": { @@ -343,7 +412,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" }, "wireframeImage": { @@ -382,7 +451,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" }, "wireframeInput": { @@ -428,7 +497,7 @@ "type": "number" } }, - "required": ["disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["disabled", "id", "inputType", "type"], "type": "object" }, "wireframeInputComponent": { @@ -494,7 +563,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" }, "wireframeProgress": { @@ -545,7 +614,7 @@ "type": "number" } }, - "required": ["disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["disabled", "id", "inputType", "type"], "type": "object" }, "wireframeRadio": { @@ -595,7 +664,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["checked", "disabled", "id", "inputType", "type"], "type": "object" }, "wireframeRadioGroup": { @@ -630,7 +699,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" }, "wireframeRectangle": { @@ -665,7 +734,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" }, "wireframeSelect": { @@ -717,7 +786,7 @@ "type": "number" } }, - "required": ["disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["disabled", "id", "inputType", "type"], "type": "object" }, "wireframeText": { @@ -755,7 +824,7 @@ "type": "number" } }, - "required": ["height", "id", "text", "type", "width", "x", "y"], + "required": ["id", "text", "type"], "type": "object" }, "wireframeTextArea": { @@ -801,7 +870,7 @@ "type": "number" } }, - "required": ["disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["disabled", "id", "inputType", "type"], "type": "object" }, "wireframeToggle": { @@ -850,7 +919,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "height", "id", "inputType", "type", "width", "x", "y"], + "required": ["checked", "disabled", "id", "inputType", "type"], "type": "object" }, "wireframeWebView": { @@ -888,7 +957,7 @@ "type": "number" } }, - "required": ["height", "id", "type", "width", "x", "y"], + "required": ["id", "type"], "type": "object" } } diff --git a/ee/frontend/mobile-replay/transform.test.ts b/ee/frontend/mobile-replay/transform.test.ts index c2488eb189121..ccd5fb097c517 100644 --- a/ee/frontend/mobile-replay/transform.test.ts +++ b/ee/frontend/mobile-replay/transform.test.ts @@ -476,6 +476,26 @@ describe('replay/transform', () => { ).toMatchSnapshot() }) + test('open keyboard custom event', () => { + expect( + posthogEEModule.mobileReplay?.transformEventToWeb({ + timestamp: 1, + type: EventType.Custom, + data: { tag: 'keyboard', payload: { open: true, height: 150 } }, + }) + ).toMatchSnapshot() + }) + + test('closed keyboard custom event', () => { + expect( + posthogEEModule.mobileReplay?.transformEventToWeb({ + timestamp: 1, + type: EventType.Custom, + data: { tag: 'keyboard', payload: { open: false } }, + }) + ).toMatchSnapshot() + }) + test('radio_group', () => { expect( posthogEEModule.mobileReplay?.transformEventToWeb({ diff --git a/ee/frontend/mobile-replay/transformers.ts b/ee/frontend/mobile-replay/transformers.ts index 34fd54b82952e..3baad87dc5069 100644 --- a/ee/frontend/mobile-replay/transformers.ts +++ b/ee/frontend/mobile-replay/transformers.ts @@ -1,10 +1,21 @@ -import { EventType, fullSnapshotEvent, incrementalSnapshotEvent, metaEvent } from '@rrweb/types' +import { + addedNodeMutation, + customEvent, + EventType, + fullSnapshotEvent, + incrementalSnapshotEvent, + IncrementalSource, + metaEvent, + mutationData, +} from '@rrweb/types' +import { captureMessage } from '@sentry/react' import { attributes, elementNode, fullSnapshotEvent as MobileFullSnapshotEvent, incrementalSnapshotEvent as MobileIncrementalSnapshotEvent, + keyboardEvent, metaEvent as MobileMetaEvent, MobileNodeType, NodeType, @@ -63,7 +74,76 @@ function* ids(): Generator { // TODO this is shared for the lifetime of the page, so a very, very long-lived session could exhaust the ids const idSequence = ids() +// there are some fixed ids that we need to use for fixed elements or artificial mutations +const DOCUMENT_ID = 1 +const HTML_DOC_TYPE_ID = 2 +const HTML_ELEMENT_ID = 3 +const HEAD_ID = 4 const BODY_ID = 5 +const KEYBOARD_ID = 6 + +function isKeyboardEvent(x: unknown): x is keyboardEvent { + return ( + typeof x === 'object' && + x !== null && + 'data' in x && + typeof x.data === 'object' && + x.data !== null && + 'tag' in x.data && + x.data.tag === 'keyboard' + ) +} + +export const makeCustomEvent = ( + mobileCustomEvent: (customEvent | keyboardEvent) & { + timestamp: number + delay?: number + } +): (customEvent | incrementalSnapshotEvent) & { + timestamp: number + delay?: number +} => { + if (isKeyboardEvent(mobileCustomEvent)) { + // keyboard events are handled as incremental snapshots to add or remove a keyboard from the DOM + // TODO eventually we can pass something to makeIncrementalEvent here + const adds: addedNodeMutation[] = [] + const removes = [] + if (mobileCustomEvent.data.payload.open) { + const keyboardPlaceHolder = makePlaceholderElement( + { + id: KEYBOARD_ID, + type: 'placeholder', + label: 'keyboard', + height: mobileCustomEvent.data.payload.height, + }, + [], + true + ) + if (keyboardPlaceHolder) { + adds.push({ + parentId: BODY_ID, + nextId: null, + node: keyboardPlaceHolder, + }) + } else { + captureMessage('Failed to create keyboard placeholder', { extra: { mobileCustomEvent } }) + } + } else { + removes.push({ + parentId: BODY_ID, + id: KEYBOARD_ID, + }) + } + const mutation: mutationData = { adds, attributes: [], removes, source: IncrementalSource.Mutation, texts: [] } + return { + type: EventType.IncrementalSnapshot, + data: mutation, + timestamp: mobileCustomEvent.timestamp, + } + } else { + return mobileCustomEvent + } +} export const makeMetaEvent = ( mobileMetaEvent: MobileMetaEvent & { @@ -135,7 +215,11 @@ function makeWebViewElement(wireframe: wireframe, children: serializedNodeWithId return makePlaceholderElement(labelledWireframe, children) } -function makePlaceholderElement(wireframe: wireframe, children: serializedNodeWithId[]): serializedNodeWithId | null { +function makePlaceholderElement( + wireframe: wireframe, + children: serializedNodeWithId[], + fullWidth?: boolean +): serializedNodeWithId | null { const txt = 'label' in wireframe && wireframe.label ? wireframe.label : wireframe.type || 'PLACEHOLDER' return { type: NodeType.Element, @@ -146,6 +230,7 @@ function makePlaceholderElement(wireframe: wireframe, children: serializedNodeWi horizontalAlign: 'center', backgroundColor: wireframe.style?.backgroundColor || BACKGROUND, color: wireframe.style?.color || FOREGROUND, + ...(fullWidth ? { width: '100vw' } : {}), }), }, id: wireframe.id, @@ -173,8 +258,8 @@ function makeImageElement(wireframe: wireframeImage, children: serializedNodeWit tagName: 'img', attributes: { src: src, - width: wireframe.width, - height: wireframe.height, + width: wireframe.width || '100vw', + height: wireframe.height || '100vh', style: makeStylesString(wireframe), }, id: wireframe.id, @@ -744,19 +829,19 @@ export const makeFullEvent = ( name: 'html', publicId: '', systemId: '', - id: 2, + id: HTML_DOC_TYPE_ID, }, { type: NodeType.Element, tagName: 'html', attributes: { style: makeHTMLStyles() }, - id: 3, + id: HTML_ELEMENT_ID, childNodes: [ { type: NodeType.Element, tagName: 'head', attributes: {}, - id: 4, + id: HEAD_ID, childNodes: [], }, { @@ -777,7 +862,7 @@ export const makeFullEvent = ( ], }, ], - id: 1, + id: DOCUMENT_ID, }, initialOffset: { top: 0, diff --git a/ee/frontend/mobile-replay/wireframeStyle.ts b/ee/frontend/mobile-replay/wireframeStyle.ts index e475995e6ee71..87db3b3527946 100644 --- a/ee/frontend/mobile-replay/wireframeStyle.ts +++ b/ee/frontend/mobile-replay/wireframeStyle.ts @@ -1,5 +1,7 @@ import { MobileStyles, wireframe, wireframeProgress } from './mobile.types' +export type StyleOverride = MobileStyles & { width?: '100vw' } + function isNumber(candidate: unknown): candidate is number { return typeof candidate === 'number' } @@ -16,7 +18,7 @@ function ensureUnit(value: string | number): string { return isNumber(value) ? `${value}px` : value.replace(/px$/g, '') + 'px' } -function makeBorderStyles(wireframe: wireframe, styleOverride?: MobileStyles): string { +function makeBorderStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { @@ -47,11 +49,15 @@ function makeBorderStyles(wireframe: wireframe, styleOverride?: MobileStyles): s return styles } -export function makePositionStyles(wireframe: wireframe): string { +export function makePositionStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' - if (isNumber(wireframe.width)) { + + if (styleOverride?.width === '100vw') { + styles += `width: 100vw;` + } else if (isNumber(wireframe.width)) { styles += `width: ${ensureUnit(wireframe.width)};` } + if (isNumber(wireframe.height)) { styles += `height: ${ensureUnit(wireframe.height)};` } @@ -70,7 +76,7 @@ export function makePositionStyles(wireframe: wireframe): string { return styles } -function makeLayoutStyles(wireframe: wireframe, styleOverride?: MobileStyles): string { +function makeLayoutStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { @@ -94,7 +100,7 @@ function makeLayoutStyles(wireframe: wireframe, styleOverride?: MobileStyles): s return styles } -function makeFontStyles(wireframe: wireframe, styleOverride?: MobileStyles): string { +function makeFontStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { @@ -115,7 +121,7 @@ function makeFontStyles(wireframe: wireframe, styleOverride?: MobileStyles): str return styles } -export function makeIndeterminateProgressStyles(wireframe: wireframeProgress, styleOverride?: MobileStyles): string { +export function makeIndeterminateProgressStyles(wireframe: wireframeProgress, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { ...wireframe.style, @@ -132,7 +138,7 @@ export function makeIndeterminateProgressStyles(wireframe: wireframeProgress, st return styles } -export function makeDeterminateProgressStyles(wireframe: wireframeProgress, styleOverride?: MobileStyles): string { +export function makeDeterminateProgressStyles(wireframe: wireframeProgress, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { ...wireframe.style, @@ -156,17 +162,17 @@ export function makeDeterminateProgressStyles(wireframe: wireframeProgress, styl /** * normally use makeStylesString instead, but sometimes you need styles without any colors applied * */ -export function makeMinimalStyles(wireframe: wireframe, styleOverride?: MobileStyles): string { +export function makeMinimalStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' - styles += makePositionStyles(wireframe) + styles += makePositionStyles(wireframe, styleOverride) styles += makeLayoutStyles(wireframe, styleOverride) styles += makeFontStyles(wireframe, styleOverride) return styles } -export function makeColorStyles(wireframe: wireframe, styleOverride?: MobileStyles): string { +export function makeColorStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' const combinedStyles = { @@ -186,7 +192,7 @@ export function makeColorStyles(wireframe: wireframe, styleOverride?: MobileStyl return styles } -export function makeStylesString(wireframe: wireframe, styleOverride?: MobileStyles): string { +export function makeStylesString(wireframe: wireframe, styleOverride?: StyleOverride): string { let styles = '' styles += makeColorStyles(wireframe, styleOverride) From 19fb07b5d2f16dd8885f1d5c6f242c5058506351 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Tue, 19 Dec 2023 13:30:02 +0000 Subject: [PATCH 2/3] fix bottom and width schema/output --- .../__snapshots__/transform.test.ts.snap | 2 +- ee/frontend/mobile-replay/mobile.types.ts | 15 +- .../schema/mobile/rr-mobile-schema.json | 182 +++++++++++++++--- ee/frontend/mobile-replay/transformers.ts | 14 +- ee/frontend/mobile-replay/wireframeStyle.ts | 39 ++-- 5 files changed, 200 insertions(+), 52 deletions(-) diff --git a/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap index a343dc74144d0..d9b55d12fbd4a 100644 --- a/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap +++ b/ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap @@ -2695,7 +2695,7 @@ exports[`replay/transform transform inputs open keyboard custom event 1`] = ` "nextId": null, "node": { "attributes": { - "style": "color: #35373e;background-color: #f3f4ef;width: 100vw;height: 150px;position: fixed;left: 0px;top: 0px;align-items: center;justify-content: center;display: flex;", + "style": "color: #35373e;background-color: #f3f4ef;width: 100vw;height: 150px;bottom: 0;position: fixed;align-items: center;justify-content: center;display: flex;", }, "childNodes": [ { diff --git a/ee/frontend/mobile-replay/mobile.types.ts b/ee/frontend/mobile-replay/mobile.types.ts index 9349eacd5ea18..992860d730918 100644 --- a/ee/frontend/mobile-replay/mobile.types.ts +++ b/ee/frontend/mobile-replay/mobile.types.ts @@ -121,13 +121,13 @@ type wireframeBase = { x?: number y?: number /* - * @description the width dimension of the element, the only accepted units is pixels. You can omit the unit. If not received it is not set + * @description the width dimension of the element, either '100vw' i.e. viewport width. Or a value in pixels. You can omit the unit when specifying pixels. */ - width?: number + width: number | '100vw' /* - * @description the height dimension of the element, the only accepted units is pixels. You can omit the unit. If not received it is not set + * @description the height dimension of the element, the only accepted units is pixels. You can omit the unit. */ - height?: number + height: number childWireframes?: wireframe[] type: MobileNodeType style?: MobileStyles @@ -302,14 +302,17 @@ export type keyboardEvent = { open: true styles?: MobileStyles /** - * @description x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting `bottom: 0` + * @description x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present then the keyboard is at the bottom of the screen */ x?: number y?: number /* - * @description width and height are the dimensions of the element, the only accepted units is pixels. You can omit the unit. A keyboard always has height, and defaults to width of the viewport + * @description the height dimension of the keyboard, the only accepted units is pixels. You can omit the unit. */ height: number + /* + * @description the width dimension of the keyboard, the only accepted units is pixels. You can omit the unit. If not present defaults to width of the viewport + */ width?: number } | { open: false } diff --git a/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json index d0bbe5ec34122..bfbdd9fd4155d 100644 --- a/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json +++ b/ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json @@ -145,7 +145,7 @@ "type": "number" }, "x": { - "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting `bottom: 0`", + "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present then the keyboard is at the bottom of the screen", "type": "number" }, "y": { @@ -317,7 +317,15 @@ "type": "string" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -327,7 +335,7 @@ "type": "number" } }, - "required": ["disabled", "id", "inputType", "type"], + "required": ["disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeCheckBox": { @@ -367,7 +375,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -377,7 +393,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "id", "inputType", "type"], + "required": ["checked", "disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeDiv": { @@ -402,7 +418,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -412,7 +436,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" }, "wireframeImage": { @@ -441,7 +465,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -451,7 +483,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" }, "wireframeInput": { @@ -487,7 +519,15 @@ "type": "string" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -497,7 +537,7 @@ "type": "number" } }, - "required": ["disabled", "id", "inputType", "type"], + "required": ["disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeInputComponent": { @@ -553,7 +593,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -563,7 +611,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" }, "wireframeProgress": { @@ -604,7 +652,15 @@ "type": "number" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -614,7 +670,7 @@ "type": "number" } }, - "required": ["disabled", "id", "inputType", "type"], + "required": ["disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeRadio": { @@ -654,7 +710,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -664,7 +728,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "id", "inputType", "type"], + "required": ["checked", "disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeRadioGroup": { @@ -689,7 +753,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -699,7 +771,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" }, "wireframeRectangle": { @@ -724,7 +796,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -734,7 +814,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" }, "wireframeSelect": { @@ -776,7 +856,15 @@ "type": "string" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -786,7 +874,7 @@ "type": "number" } }, - "required": ["disabled", "id", "inputType", "type"], + "required": ["disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeText": { @@ -814,7 +902,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -824,7 +920,7 @@ "type": "number" } }, - "required": ["id", "text", "type"], + "required": ["height", "id", "text", "type", "width"], "type": "object" }, "wireframeTextArea": { @@ -860,7 +956,15 @@ "type": "string" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -870,7 +974,7 @@ "type": "number" } }, - "required": ["disabled", "id", "inputType", "type"], + "required": ["disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeToggle": { @@ -909,7 +1013,15 @@ "$ref": "#/definitions/MobileNodeType" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -919,7 +1031,7 @@ "type": "number" } }, - "required": ["checked", "disabled", "id", "inputType", "type"], + "required": ["checked", "disabled", "height", "id", "inputType", "type", "width"], "type": "object" }, "wireframeWebView": { @@ -947,7 +1059,15 @@ "type": "string" }, "width": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "const": "100vw", + "type": "string" + } + ] }, "x": { "description": "x and y are the top left corner of the element, if they are present then the element is absolutely positioned, if they are not present this is equivalent to setting them to 0", @@ -957,7 +1077,7 @@ "type": "number" } }, - "required": ["id", "type"], + "required": ["height", "id", "type", "width"], "type": "object" } } diff --git a/ee/frontend/mobile-replay/transformers.ts b/ee/frontend/mobile-replay/transformers.ts index 3baad87dc5069..35e0d079a0650 100644 --- a/ee/frontend/mobile-replay/transformers.ts +++ b/ee/frontend/mobile-replay/transformers.ts @@ -45,6 +45,7 @@ import { makeMinimalStyles, makePositionStyles, makeStylesString, + StyleOverride, } from './wireframeStyle' const BACKGROUND = '#f3f4ef' @@ -109,15 +110,22 @@ export const makeCustomEvent = ( const adds: addedNodeMutation[] = [] const removes = [] if (mobileCustomEvent.data.payload.open) { + const shouldAbsolutelyPosition = + _isPositiveInteger(mobileCustomEvent.data.payload.x) || + _isPositiveInteger(mobileCustomEvent.data.payload.y) + const styleOverride: StyleOverride | undefined = shouldAbsolutelyPosition ? undefined : { bottom: true } const keyboardPlaceHolder = makePlaceholderElement( { id: KEYBOARD_ID, type: 'placeholder', label: 'keyboard', height: mobileCustomEvent.data.payload.height, + width: _isPositiveInteger(mobileCustomEvent.data.payload.width) + ? mobileCustomEvent.data.payload.width + : '100vw', }, [], - true + styleOverride ) if (keyboardPlaceHolder) { adds.push({ @@ -218,7 +226,7 @@ function makeWebViewElement(wireframe: wireframe, children: serializedNodeWithId function makePlaceholderElement( wireframe: wireframe, children: serializedNodeWithId[], - fullWidth?: boolean + styleOverride?: StyleOverride ): serializedNodeWithId | null { const txt = 'label' in wireframe && wireframe.label ? wireframe.label : wireframe.type || 'PLACEHOLDER' return { @@ -230,7 +238,7 @@ function makePlaceholderElement( horizontalAlign: 'center', backgroundColor: wireframe.style?.backgroundColor || BACKGROUND, color: wireframe.style?.color || FOREGROUND, - ...(fullWidth ? { width: '100vw' } : {}), + ...styleOverride, }), }, id: wireframe.id, diff --git a/ee/frontend/mobile-replay/wireframeStyle.ts b/ee/frontend/mobile-replay/wireframeStyle.ts index 87db3b3527946..fb5d41c762fb4 100644 --- a/ee/frontend/mobile-replay/wireframeStyle.ts +++ b/ee/frontend/mobile-replay/wireframeStyle.ts @@ -1,6 +1,9 @@ import { MobileStyles, wireframe, wireframeProgress } from './mobile.types' -export type StyleOverride = MobileStyles & { width?: '100vw' } +// StyleOverride is defined here and not in the schema +// because these are overrides that the transformer is allowed to make +// not that clients are allowed to request +export type StyleOverride = MobileStyles & { bottom?: true } function isNumber(candidate: unknown): candidate is number { return typeof candidate === 'number' @@ -49,10 +52,10 @@ function makeBorderStyles(wireframe: wireframe, styleOverride?: StyleOverride): return styles } -export function makePositionStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { +export function makeDimensionStyles(wireframe: wireframe): string { let styles = '' - if (styleOverride?.width === '100vw') { + if (wireframe.width === '100vw') { styles += `width: 100vw;` } else if (isNumber(wireframe.width)) { styles += `width: ${ensureUnit(wireframe.width)};` @@ -62,17 +65,31 @@ export function makePositionStyles(wireframe: wireframe, styleOverride?: StyleOv styles += `height: ${ensureUnit(wireframe.height)};` } - const posX = wireframe.x || 0 - const posY = wireframe.y || 0 - if (isNumber(posX) || isNumber(posY)) { + return styles +} + +export function makePositionStyles(wireframe: wireframe, styleOverride?: StyleOverride): string { + let styles = '' + + styles += makeDimensionStyles(wireframe) + + if (styleOverride?.bottom) { + styles += `bottom: 0;` styles += `position: fixed;` - if (isNumber(posX)) { - styles += `left: ${ensureUnit(posX)};` - } - if (isNumber(posY)) { - styles += `top: ${ensureUnit(posY)};` + } else { + const posX = wireframe.x || 0 + const posY = wireframe.y || 0 + if (isNumber(posX) || isNumber(posY)) { + styles += `position: fixed;` + if (isNumber(posX)) { + styles += `left: ${ensureUnit(posX)};` + } + if (isNumber(posY)) { + styles += `top: ${ensureUnit(posY)};` + } } } + return styles } From 5eb9684a9556ee2cf4ce4100fbe678bbcefd2af3 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Tue, 19 Dec 2023 13:36:11 +0000 Subject: [PATCH 3/3] fix --- ee/frontend/mobile-replay/transformers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/frontend/mobile-replay/transformers.ts b/ee/frontend/mobile-replay/transformers.ts index 35e0d079a0650..b75b77cf55697 100644 --- a/ee/frontend/mobile-replay/transformers.ts +++ b/ee/frontend/mobile-replay/transformers.ts @@ -266,8 +266,8 @@ function makeImageElement(wireframe: wireframeImage, children: serializedNodeWit tagName: 'img', attributes: { src: src, - width: wireframe.width || '100vw', - height: wireframe.height || '100vh', + width: wireframe.width, + height: wireframe.height, style: makeStylesString(wireframe), }, id: wireframe.id,