From f1fade81a359a09016e28c5983183e24f5b1516d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 Aug 2021 16:30:43 +0530 Subject: [PATCH 01/12] fix(ui) - clear event filters --- .../Session_/EventsBlock/EventSearch/EventSearch.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js index aa7b12476..1f7dbbb46 100644 --- a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js +++ b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js @@ -1,9 +1,15 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import { Input, Icon } from 'UI' export default function EventSearch(props) { const { onChange, clearSearch, value, header } = props; const [showSearch, setShowSearch] = useState(false) + + useEffect(() => { + return () => { + clearSearch() + } + }, []) return (
From 0920f17009a91f9d9c2a65af3655b0bcbbde829e Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 Aug 2021 17:27:05 +0530 Subject: [PATCH 02/12] fix(ui) - assist check for video and audio --- .../AssistActions/AssistActions.tsx | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 8ac60e4ba..c596fcb1f 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -40,22 +40,30 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus toast.info(`Call was rejected.`); } - function onError() { - toast.error(`Something went wrong!`); + function onError(e) { + toast.error(e); } - function call() { + function onCallConnect(lStream) { + setLocalStream(lStream); + setEndCall(() => callPeer( + lStream, + setIncomeStream, + onClose.bind(null, lStream), + onReject, + onError + )); + } + + function call() { navigator.mediaDevices.getUserMedia({video:true, audio:true}) - .then(lStream => { - setLocalStream(lStream); - setEndCall(() => callPeer( - lStream, - setIncomeStream, - onClose.bind(null, lStream), - onReject, - onError - )); - }).catch(onError); + .then(onCallConnect).catch(error => { + onError(error) + + navigator.mediaDevices.getUserMedia({audio:true}) + .then(onCallConnect) + .catch(onError) + }); } const inCall = calling !== CallingState.False; From 57ff3574b5a70005d7a08a59a19b1dfffe7f28c3 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 21:37:47 +0800 Subject: [PATCH 03/12] fix(tracker): put vendor lib inside src for the cjs compilation --- tracker/tracker/package-lock.json | 2 +- tracker/tracker/package.json | 2 +- tracker/tracker/src/main/modules/mouse.ts | 2 +- .../tracker/src/main/vendors/finder/LICENSE | 21 + .../tracker/src/main/vendors/finder/README.md | 132 ++++++ .../src/main/vendors/finder/finder.d.ts | 12 + .../tracker/src/main/vendors/finder/finder.js | 339 ++++++++++++++ .../tracker/src/main/vendors/finder/finder.ts | 414 ++++++++++++++++++ .../src/main/vendors/finder/package.json | 78 ++++ 9 files changed, 999 insertions(+), 3 deletions(-) create mode 100644 tracker/tracker/src/main/vendors/finder/LICENSE create mode 100644 tracker/tracker/src/main/vendors/finder/README.md create mode 100644 tracker/tracker/src/main/vendors/finder/finder.d.ts create mode 100644 tracker/tracker/src/main/vendors/finder/finder.js create mode 100644 tracker/tracker/src/main/vendors/finder/finder.ts create mode 100644 tracker/tracker/src/main/vendors/finder/package.json diff --git a/tracker/tracker/package-lock.json b/tracker/tracker/package-lock.json index 4af49d03f..62bc7c1b6 100644 --- a/tracker/tracker/package-lock.json +++ b/tracker/tracker/package-lock.json @@ -1,6 +1,6 @@ { "name": "@openreplay/tracker", - "version": "3.1.0", + "version": "3.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 822107f58..eb760e700 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.2.0", + "version": "3.2.1", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 73e3672b9..cae32142f 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -1,4 +1,4 @@ -import { finder } from '@medv/finder'; +import { finder } from '../vendors/finder/finder'; import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils'; import App from '../app'; import { MouseMove, MouseClick } from '../../messages'; diff --git a/tracker/tracker/src/main/vendors/finder/LICENSE b/tracker/tracker/src/main/vendors/finder/LICENSE new file mode 100644 index 000000000..1c1d11619 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018–2020 Anton Medvedev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tracker/tracker/src/main/vendors/finder/README.md b/tracker/tracker/src/main/vendors/finder/README.md new file mode 100644 index 000000000..391bfee9a --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/README.md @@ -0,0 +1,132 @@ +![finder](https://medv.io/assets/finder.png) + +# finder + +[![npm](https://img.shields.io/npm/v/@medv/finder?color=grightgreen)](https://www.npmjs.com/package/@medv/finder) +[![Build status](https://img.shields.io/travis/antonmedv/finder)](https://travis-ci.org/antonmedv/finder) +[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@medv/finder?label=size)](https://bundlephobia.com/result?p=@medv/finder) + +> CSS Selector Generator + +## Features + +* Generates **shortest** selectors +* **Unique** selectors per page +* Stable and **robust** selectors +* **2.1 kB** gzip and minify size + +## Install + +```bash +npm install @medv/finder +``` + +Finder can be used via modules: + +```html + +``` + +## Usage + +```js +import {finder} from '@medv/finder' + +document.addEventListener('click', event => { + const selector = finder(event.target) + console.log(selector) +}) +``` + +## Example + +Example of generated selector: + +```css +.blog > article:nth-child(3) .add-comment +``` + +## Configuration + +`finder` takes configuration object as second parameters. Here is example of all params with default values: + +```js +const selector = finder(event.target, { + root: document.body, + className: (name) => true, + tagName: (name) => true, + attr: (name, value) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10_000, +}) +``` + +#### `root: Element` + +Root of search, defaults to `document.body`. + +#### `idName: (name: string) => boolean` + +Check if this ID can be used. For example you can restrict using framework specific IDs: + +```js +const selector = finder(event.target, { + idName: name => !name.startsWith('ember') +}) +``` + +#### `className: (name: string) => boolean` + +Check if this class name can be used. For example you can restrict using _is-*_ class names: + +```js +const selector = finder(event.target, { + className: name => !name.startsWith('is-') +}) +``` + +#### `tagName: (name: string) => boolean` + +Check if tag name can be used, same as `className`. + +#### `attr: (name: string, value: string) => boolean` + +Check if attr name can be used. + +#### `seedMinLength: number` + +Minimum length of levels in fining selector. Starts from `1`. +For more robust selectors give this param value around 4-5 depending on depth of you DOM tree. +If `finder` hits `root` this param is ignored. + +#### `optimizedMinLength: number` + +Minimum length for optimising selector. Starts from `2`. +For example selector `body > div > div > p` can be optimized to `body p`. + +#### `threshold: number` + +Max number of selectors to check before falling into `nth-child` usage. +Checking for uniqueness of selector is very costs operation, if you have DOM tree depth of 5, with 5 classes on each level, +that gives you more than 3k selectors to check. +`finder` uses two step approach so it's reaching this threshold in some cases twice. +Default `1000` is good enough in most cases. + +#### `maxNumberOfTries: number` + +Max number of tries when we do the optimization. It is a trade-off between optimization and efficiency. +Default `10_000` is good enough in most cases. + +### Google Chrome Extension + +![Chrome Extension](https://user-images.githubusercontent.com/141232/36737287-4a999d84-1c0d-11e8-8a14-43bcf9baf7ca.png) + +Generate the unique selectors in your browser by using [Chrome Extension](https://chrome.google.com/webstore/detail/get-unique-css-selector/lkfaghhbdebclkklgjhhonadomejckai) + +## License + +[MIT](LICENSE) diff --git a/tracker/tracker/src/main/vendors/finder/finder.d.ts b/tracker/tracker/src/main/vendors/finder/finder.d.ts new file mode 100644 index 000000000..aaff849fb --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.d.ts @@ -0,0 +1,12 @@ +export declare type Options = { + root: Element; + idName: (name: string) => boolean; + className: (name: string) => boolean; + tagName: (name: string) => boolean; + attr: (name: string, value: string) => boolean; + seedMinLength: number; + optimizedMinLength: number; + threshold: number; + maxNumberOfTries: number; +}; +export declare function finder(input: Element, options?: Partial): string; diff --git a/tracker/tracker/src/main/vendors/finder/finder.js b/tracker/tracker/src/main/vendors/finder/finder.js new file mode 100644 index 000000000..0e5eab2d7 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.js @@ -0,0 +1,339 @@ +var Limit; +(function (Limit) { + Limit[Limit["All"] = 0] = "All"; + Limit[Limit["Two"] = 1] = "Two"; + Limit[Limit["One"] = 2] = "One"; +})(Limit || (Limit = {})); +let config; +let rootDocument; +export function finder(input, options) { + if (input.nodeType !== Node.ELEMENT_NODE) { + throw new Error(`Can't generate CSS selector for non-element node type.`); + } + if ("html" === input.tagName.toLowerCase()) { + return "html"; + } + const defaults = { + root: document.body, + idName: (name) => true, + className: (name) => true, + tagName: (name) => true, + attr: (name, value) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10000, + }; + config = Object.assign(Object.assign({}, defaults), options); + rootDocument = findRootDocument(config.root, defaults); + let path = bottomUpSearch(input, Limit.All, () => bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One))); + if (path) { + const optimized = sort(optimize(path, input)); + if (optimized.length > 0) { + path = optimized[0]; + } + return selector(path); + } + else { + throw new Error(`Selector was not found.`); + } +} +function findRootDocument(rootNode, defaults) { + if (rootNode.nodeType === Node.DOCUMENT_NODE) { + return rootNode; + } + if (rootNode === defaults.root) { + return rootNode.ownerDocument; + } + return rootNode; +} +function bottomUpSearch(input, limit, fallback) { + let path = null; + let stack = []; + let current = input; + let i = 0; + while (current && current !== config.root.parentElement) { + let level = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()]; + const nth = index(current); + if (limit === Limit.All) { + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))); + } + } + else if (limit === Limit.Two) { + level = level.slice(0, 1); + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))); + } + } + else if (limit === Limit.One) { + const [node] = level = level.slice(0, 1); + if (nth && dispensableNth(node)) { + level = [nthChild(node, nth)]; + } + } + for (let node of level) { + node.level = i; + } + stack.push(level); + if (stack.length >= config.seedMinLength) { + path = findUniquePath(stack, fallback); + if (path) { + break; + } + } + current = current.parentElement; + i++; + } + if (!path) { + path = findUniquePath(stack, fallback); + } + return path; +} +function findUniquePath(stack, fallback) { + const paths = sort(combinations(stack)); + if (paths.length > config.threshold) { + return fallback ? fallback() : null; + } + for (let candidate of paths) { + if (unique(candidate)) { + return candidate; + } + } + return null; +} +function selector(path) { + let node = path[0]; + let query = node.name; + for (let i = 1; i < path.length; i++) { + const level = path[i].level || 0; + if (node.level === level - 1) { + query = `${path[i].name} > ${query}`; + } + else { + query = `${path[i].name} ${query}`; + } + node = path[i]; + } + return query; +} +function penalty(path) { + return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0); +} +function unique(path) { + switch (rootDocument.querySelectorAll(selector(path)).length) { + case 0: + throw new Error(`Can't select any node with this selector: ${selector(path)}`); + case 1: + return true; + default: + return false; + } +} +function id(input) { + const elementId = input.getAttribute("id"); + if (elementId && config.idName(elementId)) { + return { + name: "#" + cssesc(elementId, { isIdentifier: true }), + penalty: 0, + }; + } + return null; +} +function attr(input) { + const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)); + return attrs.map((attr) => ({ + name: "[" + cssesc(attr.name, { isIdentifier: true }) + "=\"" + cssesc(attr.value) + "\"]", + penalty: 0.5 + })); +} +function classNames(input) { + const names = Array.from(input.classList) + .filter(config.className); + return names.map((name) => ({ + name: "." + cssesc(name, { isIdentifier: true }), + penalty: 1 + })); +} +function tagName(input) { + const name = input.tagName.toLowerCase(); + if (config.tagName(name)) { + return { + name, + penalty: 2 + }; + } + return null; +} +function any() { + return { + name: "*", + penalty: 3 + }; +} +function index(input) { + const parent = input.parentNode; + if (!parent) { + return null; + } + let child = parent.firstChild; + if (!child) { + return null; + } + let i = 0; + while (child) { + if (child.nodeType === Node.ELEMENT_NODE) { + i++; + } + if (child === input) { + break; + } + child = child.nextSibling; + } + return i; +} +function nthChild(node, i) { + return { + name: node.name + `:nth-child(${i})`, + penalty: node.penalty + 1 + }; +} +function dispensableNth(node) { + return node.name !== "html" && !node.name.startsWith("#"); +} +function maybe(...level) { + const list = level.filter(notEmpty); + if (list.length > 0) { + return list; + } + return null; +} +function notEmpty(value) { + return value !== null && value !== undefined; +} +function* combinations(stack, path = []) { + if (stack.length > 0) { + for (let node of stack[0]) { + yield* combinations(stack.slice(1, stack.length), path.concat(node)); + } + } + else { + yield path; + } +} +function sort(paths) { + return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)); +} +function* optimize(path, input, scope = { + counter: 0, + visited: new Map() +}) { + if (path.length > 2 && path.length > config.optimizedMinLength) { + for (let i = 1; i < path.length - 1; i++) { + if (scope.counter > config.maxNumberOfTries) { + return; // Okay At least I tried! + } + scope.counter += 1; + const newPath = [...path]; + newPath.splice(i, 1); + const newPathKey = selector(newPath); + if (scope.visited.has(newPathKey)) { + return; + } + if (unique(newPath) && same(newPath, input)) { + yield newPath; + scope.visited.set(newPathKey, true); + yield* optimize(newPath, input, scope); + } + } + } +} +function same(path, input) { + return rootDocument.querySelector(selector(path)) === input; +} +const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/; +const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/; +const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; +const defaultOptions = { + "escapeEverything": false, + "isIdentifier": false, + "quotes": "single", + "wrap": false +}; +function cssesc(string, opt = {}) { + const options = Object.assign(Object.assign({}, defaultOptions), opt); + if (options.quotes != "single" && options.quotes != "double") { + options.quotes = "single"; + } + const quote = options.quotes == "double" ? "\"" : "'"; + const isIdentifier = options.isIdentifier; + const firstChar = string.charAt(0); + let output = ""; + let counter = 0; + const length = string.length; + while (counter < length) { + const character = string.charAt(counter++); + let codePoint = character.charCodeAt(0); + let value = void 0; + // If it’s not a printable ASCII character… + if (codePoint < 0x20 || codePoint > 0x7E) { + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + // It’s a high surrogate, and there is a next character. + const extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { + // next character is low surrogate + codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000; + } + else { + // It’s an unmatched surrogate; only append this code unit, in case + // the next code unit is the high surrogate of a surrogate pair. + counter--; + } + } + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + else { + if (options.escapeEverything) { + if (regexAnySingleEscape.test(character)) { + value = "\\" + character; + } + else { + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + } + else if (/[\t\n\f\r\x0B]/.test(character)) { + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + else if (character == "\\" || !isIdentifier && (character == "\"" && quote == character || character == "'" && quote == character) || isIdentifier && regexSingleEscape.test(character)) { + value = "\\" + character; + } + else { + value = character; + } + } + output += value; + } + if (isIdentifier) { + if (/^-[-\d]/.test(output)) { + output = "\\-" + output.slice(1); + } + else if (/\d/.test(firstChar)) { + output = "\\3" + firstChar + " " + output.slice(1); + } + } + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0; + } + // Strip the space. + return ($1 || "") + $2; + }); + if (!isIdentifier && options.wrap) { + return quote + output + quote; + } + return output; +} diff --git a/tracker/tracker/src/main/vendors/finder/finder.ts b/tracker/tracker/src/main/vendors/finder/finder.ts new file mode 100644 index 000000000..bb2621d75 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.ts @@ -0,0 +1,414 @@ +type Node = { + name: string + penalty: number + level?: number +} + +type Path = Node[] + +enum Limit { + All, + Two, + One, +} + +export type Options = { + root: Element + idName: (name: string) => boolean + className: (name: string) => boolean + tagName: (name: string) => boolean + attr: (name: string, value: string) => boolean + seedMinLength: number + optimizedMinLength: number + threshold: number + maxNumberOfTries: number +} + +let config: Options + +let rootDocument: Document | Element +export function finder(input: Element, options?: Partial) { + if (input.nodeType !== Node.ELEMENT_NODE) { + throw new Error(`Can't generate CSS selector for non-element node type.`) + } + + if ("html" === input.tagName.toLowerCase()) { + return "html" + } + + const defaults: Options = { + root: document.body, + idName: (name: string) => true, + className: (name: string) => true, + tagName: (name: string) => true, + attr: (name: string, value: string) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10000, + } + + config = {...defaults, ...options} + + rootDocument = findRootDocument(config.root, defaults) + + let path = + bottomUpSearch(input, Limit.All, () => + bottomUpSearch(input, Limit.Two, () => + bottomUpSearch(input, Limit.One))) + + if (path) { + const optimized = sort(optimize(path, input)) + + if (optimized.length > 0) { + path = optimized[0] + } + + return selector(path) + } else { + throw new Error(`Selector was not found.`) + } +} + +function findRootDocument(rootNode: Element | Document, defaults: Options) { + if (rootNode.nodeType === Node.DOCUMENT_NODE) { + return rootNode + } + if (rootNode === defaults.root) { + return rootNode.ownerDocument as Document + } + return rootNode +} + +function bottomUpSearch(input: Element, limit: Limit, fallback?: () => Path | null): Path | null { + let path: Path | null = null + let stack: Node[][] = [] + let current: Element | null = input + let i = 0 + + while (current && current !== config.root.parentElement) { + let level: Node[] = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()] + + const nth = index(current) + + if (limit === Limit.All) { + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + } + } else if (limit === Limit.Two) { + level = level.slice(0, 1) + + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + } + } else if (limit === Limit.One) { + const [node] = level = level.slice(0, 1) + + if (nth && dispensableNth(node)) { + level = [nthChild(node, nth)] + } + } + + for (let node of level) { + node.level = i + } + + stack.push(level) + + if (stack.length >= config.seedMinLength) { + path = findUniquePath(stack, fallback) + if (path) { + break + } + } + + current = current.parentElement + i++ + } + + if (!path) { + path = findUniquePath(stack, fallback) + } + + return path +} + +function findUniquePath(stack: Node[][], fallback?: () => Path | null): Path | null { + const paths = sort(combinations(stack)) + + if (paths.length > config.threshold) { + return fallback ? fallback() : null + } + + for (let candidate of paths) { + if (unique(candidate)) { + return candidate + } + } + + return null +} + +function selector(path: Path): string { + let node = path[0] + let query = node.name + for (let i = 1; i < path.length; i++) { + const level = path[i].level || 0 + + if (node.level === level - 1) { + query = `${path[i].name} > ${query}` + } else { + query = `${path[i].name} ${query}` + } + + node = path[i] + } + return query +} + +function penalty(path: Path): number { + return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0) +} + +function unique(path: Path) { + switch (rootDocument.querySelectorAll(selector(path)).length) { + case 0: + throw new Error(`Can't select any node with this selector: ${selector(path)}`) + case 1: + return true + default: + return false + } +} + +function id(input: Element): Node | null { + const elementId = input.getAttribute("id") + if (elementId && config.idName(elementId)) { + return { + name: "#" + cssesc(elementId, {isIdentifier: true}), + penalty: 0, + } + } + return null +} + +function attr(input: Element): Node[] { + const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)) + + return attrs.map((attr): Node => ({ + name: "[" + cssesc(attr.name, {isIdentifier: true}) + "=\"" + cssesc(attr.value) + "\"]", + penalty: 0.5 + })) +} + +function classNames(input: Element): Node[] { + const names = Array.from(input.classList) + .filter(config.className) + + return names.map((name): Node => ({ + name: "." + cssesc(name, {isIdentifier: true}), + penalty: 1 + })) +} + +function tagName(input: Element): Node | null { + const name = input.tagName.toLowerCase() + if (config.tagName(name)) { + return { + name, + penalty: 2 + } + } + return null +} + +function any(): Node { + return { + name: "*", + penalty: 3 + } +} + +function index(input: Element): number | null { + const parent = input.parentNode + if (!parent) { + return null + } + + let child = parent.firstChild + if (!child) { + return null + } + + let i = 0 + while (child) { + if (child.nodeType === Node.ELEMENT_NODE) { + i++ + } + + if (child === input) { + break + } + + child = child.nextSibling + } + + return i +} + +function nthChild(node: Node, i: number): Node { + return { + name: node.name + `:nth-child(${i})`, + penalty: node.penalty + 1 + } +} + +function dispensableNth(node: Node) { + return node.name !== "html" && !node.name.startsWith("#") +} + +function maybe(...level: (Node | null)[]): Node[] | null { + const list = level.filter(notEmpty) + if (list.length > 0) { + return list + } + return null +} + +function notEmpty(value: T | null | undefined): value is T { + return value !== null && value !== undefined +} + +function* combinations(stack: Node[][], path: Node[] = []): Generator { + if (stack.length > 0) { + for (let node of stack[0]) { + yield* combinations(stack.slice(1, stack.length), path.concat(node)) + } + } else { + yield path + } +} + +function sort(paths: Iterable): Path[] { + return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)) +} + +type Scope = { + counter: number + visited: Map +} + +function* optimize(path: Path, input: Element, scope: Scope = { + counter: 0, + visited: new Map() +}): Generator { + if (path.length > 2 && path.length > config.optimizedMinLength) { + for (let i = 1; i < path.length - 1; i++) { + if (scope.counter > config.maxNumberOfTries) { + return // Okay At least I tried! + } + scope.counter += 1 + const newPath = [...path] + newPath.splice(i, 1) + const newPathKey = selector(newPath) + if (scope.visited.has(newPathKey)) { + return + } + if (unique(newPath) && same(newPath, input)) { + yield newPath + scope.visited.set(newPathKey, true) + yield* optimize(newPath, input, scope) + } + } + } +} + +function same(path: Path, input: Element) { + return rootDocument.querySelector(selector(path)) === input +} + +const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/ +const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/ +const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g + +const defaultOptions = { + "escapeEverything": false, + "isIdentifier": false, + "quotes": "single", + "wrap": false +} + +function cssesc(string: string, opt: Partial = {}) { + const options = {...defaultOptions, ...opt} + if (options.quotes != "single" && options.quotes != "double") { + options.quotes = "single" + } + const quote = options.quotes == "double" ? "\"" : "'" + const isIdentifier = options.isIdentifier + + const firstChar = string.charAt(0) + let output = "" + let counter = 0 + const length = string.length + while (counter < length) { + const character = string.charAt(counter++) + let codePoint = character.charCodeAt(0) + let value: string | undefined = void 0 + // If it’s not a printable ASCII character… + if (codePoint < 0x20 || codePoint > 0x7E) { + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + // It’s a high surrogate, and there is a next character. + const extra = string.charCodeAt(counter++) + if ((extra & 0xFC00) == 0xDC00) { + // next character is low surrogate + codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000 + } else { + // It’s an unmatched surrogate; only append this code unit, in case + // the next code unit is the high surrogate of a surrogate pair. + counter-- + } + } + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } else { + if (options.escapeEverything) { + if (regexAnySingleEscape.test(character)) { + value = "\\" + character + } else { + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } + } else if (/[\t\n\f\r\x0B]/.test(character)) { + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } else if (character == "\\" || !isIdentifier && (character == "\"" && quote == character || character == "'" && quote == character) || isIdentifier && regexSingleEscape.test(character)) { + value = "\\" + character + } else { + value = character + } + } + output += value + } + + if (isIdentifier) { + if (/^-[-\d]/.test(output)) { + output = "\\-" + output.slice(1) + } else if (/\d/.test(firstChar)) { + output = "\\3" + firstChar + " " + output.slice(1) + } + } + + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0 + } + // Strip the space. + return ($1 || "") + $2 + }) + + if (!isIdentifier && options.wrap) { + return quote + output + quote + } + return output +} diff --git a/tracker/tracker/src/main/vendors/finder/package.json b/tracker/tracker/src/main/vendors/finder/package.json new file mode 100644 index 000000000..685be35f4 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/package.json @@ -0,0 +1,78 @@ +{ + "_from": "@medv/finder", + "_id": "@medv/finder@2.0.0", + "_inBundle": false, + "_integrity": "sha512-gV4jOsGpiWNDGd8Dw7tod1Fc9Gc7StaOT4oZ/6srHRWtsHU+HYWzmkYsa3Qy/z0e9tY1WpJ9wWdBFGskfbzoug==", + "_location": "/@medv/finder", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "@medv/finder", + "name": "@medv/finder", + "escapedName": "@medv%2ffinder", + "scope": "@medv", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/@medv/finder/-/finder-2.0.0.tgz", + "_shasum": "699b7141393aa815f120b38f54f92ad212225902", + "_spec": "@medv/finder", + "_where": "/Users/shikhu/work/openreplay/tracker/tracker", + "author": { + "name": "Anton Medvedev", + "email": "anton@medv.io" + }, + "ava": { + "require": [ + "esm", + "./test/helpers/setup-browser-env.js" + ] + }, + "bugs": { + "url": "https://github.com/antonmedv/finder/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "CSS Selector Generator", + "devDependencies": { + "ava": "^3.8.2", + "babel-minify": "*", + "browser-env": "^3.3.0", + "esm": "^3.2.25", + "gzip-size-cli": "*", + "release-it": "^13.6.1", + "typescript": "3.9.3" + }, + "files": [ + "*.ts", + "*.js" + ], + "homepage": "https://github.com/antonmedv/finder", + "keywords": [ + "css", + "selector", + "generator" + ], + "license": "MIT", + "main": "finder.js", + "name": "@medv/finder", + "repository": { + "type": "git", + "url": "git+https://github.com/antonmedv/finder.git" + }, + "scripts": { + "prepare": "tsc", + "release": "release-it --access public", + "size": "minify finder.js --sourceType module | gzip-size", + "start": "tsc -w", + "test": "tsc && ava" + }, + "types": "finder.d.ts", + "version": "2.0.0" +} From b40980e85f001de3ba59cd830fdfbb99612bfaa4 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 21:38:29 +0800 Subject: [PATCH 04/12] feat(frontend): consider create_element_node as activity (temp) --- frontend/app/player/MessageDistributor/MessageDistributor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index ec539e53f..e8d807593 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -337,6 +337,8 @@ export default class MessageDistributor extends StatedScreen { distributeMessage = (msg: TimedMessage, index: number): void => { if ([ "mouse_move", + "mouse_click", + "create_element_node", // not a user activity, though visual change "set_input_value", "set_input_checked", "set_viewport_size", From b2d986867391e6fe0cac45bbf75d1a2c07c727ab Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 22:06:59 +0800 Subject: [PATCH 05/12] feat(tracker-assist): 3.0.1 - fallback to audio-only mediaDevice request --- .../Assist/components/AssistActions/AssistActions.tsx | 4 +--- .../MessageDistributor/managers/AssistManager.ts | 1 - tracker/tracker-assist/package-lock.json | 2 +- tracker/tracker-assist/package.json | 2 +- tracker/tracker-assist/src/index.ts | 10 ++++++++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index c596fcb1f..0d51cca91 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -57,9 +57,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus function call() { navigator.mediaDevices.getUserMedia({video:true, audio:true}) - .then(onCallConnect).catch(error => { - onError(error) - + .then(onCallConnect).catch(error => { // TODO retry only if specific error navigator.mediaDevices.getUserMedia({audio:true}) .then(onCallConnect) .catch(onError) diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 26e683bd2..92df86967 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -319,7 +319,6 @@ export default class AssistManager { return; } // @ts-ignore - this.md.display(false); this.dataConnection?.close(); this.setStatus(ConnectionStatus.Disconnected); }, 8000); // TODO: more convenient way diff --git a/tracker/tracker-assist/package-lock.json b/tracker/tracker-assist/package-lock.json index 4ccf9ba06..82d1c013c 100644 --- a/tracker/tracker-assist/package-lock.json +++ b/tracker/tracker-assist/package-lock.json @@ -1,6 +1,6 @@ { "name": "@openreplay/tracker-assist", - "version": "3.0.0", + "version": "3.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 581eb7cd4..0cf18a096 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "3.0.0", + "version": "3.0.1", "keywords": [ "WebRTC", "assistance", diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index 910c4d3e8..9a75c8cb8 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -116,8 +116,7 @@ export default function(opts: Partial = {}) { const mouse = new Mouse(); let callUI; - navigator.mediaDevices.getUserMedia({video:true, audio:true}) - .then(lStream => { + const onCallConnect = lStream => { const onCallEnd = () => { console.log("on callend", call.open) mouse.remove(); @@ -179,6 +178,13 @@ export default function(opts: Partial = {}) { } }); }); + } + + navigator.mediaDevices.getUserMedia({video:true, audio:true}) + .then(onCallConnect) + .catch(e => { // TODO retry only if specific error + navigator.mediaDevices.getUserMedia({audio:true}) // in case there is no camera on device + .then(onCallConnect); }); }); }); From d5b1c7dcecf49f308b85642f01c5ab4b8ea62a7e Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 22:19:53 +0800 Subject: [PATCH 06/12] fix(sourcemap-uploader): 3.0.5 error message fix --- sourcemap-uploader/cli.js | 2 +- sourcemap-uploader/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sourcemap-uploader/cli.js b/sourcemap-uploader/cli.js index fa284f1a4..253a72ce5 100755 --- a/sourcemap-uploader/cli.js +++ b/sourcemap-uploader/cli.js @@ -73,6 +73,6 @@ try { ? console.log(`Successfully uploaded ${sourceFiles.length} sourcemap file${sourceFiles.length > 1 ? "s" : ""} for: \n` + sourceFiles.join("\t\n") ) - : console.log(`No sourcemaps found in ${ args.js_dir_url }`) + : console.log(`No sourcemaps found in ${ args.sourcemap_dir_path }`) ) .catch(e => console.error(`Sourcemap Uploader: ${e}`)); diff --git a/sourcemap-uploader/package.json b/sourcemap-uploader/package.json index 439ef44d8..b68c656e1 100644 --- a/sourcemap-uploader/package.json +++ b/sourcemap-uploader/package.json @@ -1,6 +1,6 @@ { "name": "@openreplay/sourcemap-uploader", - "version": "3.0.4", + "version": "3.0.5", "description": "NPM module to upload your JS sourcemaps files to OpenReplay", "bin": "cli.js", "main": "index.js", From f25552f91c7f41dfecd0bacac5b44c943ad48b6a Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 23:38:03 +0800 Subject: [PATCH 07/12] log(assist-frontend):log peer error --- .../app/player/MessageDistributor/managers/AssistManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 92df86967..8f8b16e6c 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -155,7 +155,10 @@ export default class AssistManager { }); this.peer = peer; peer.on('error', e => { - if (['peer-unavailable', 'network'].includes(e.type)) { + if (e.type !== 'peer-unavailable') { + console.warn("AssistManager PeerJS peer error: ", e.type, e) + } + if (['peer-unavailable', 'network', 'webrtc'].includes(e.type)) { if (this.peer && this.connectionAttempts++ < MAX_RECONNECTION_COUNT) { this.setStatus(ConnectionStatus.Connecting); console.log("peerunavailable") From 522b105449959898538756387ae2ef24c1af624c Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 23:47:35 +0800 Subject: [PATCH 08/12] log(tracker-assist): convinient logs --- tracker/tracker-assist/package.json | 2 +- tracker/tracker-assist/src/index.ts | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 0cf18a096..6064782c2 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "3.0.1", + "version": "3.0.2", "keywords": [ "WebRTC", "assistance", diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index 9a75c8cb8..273907687 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -43,14 +43,15 @@ export default function(opts: Partial = {}) { path: '/assist', port: location.protocol === 'http:' && appOptions.__DISABLE_SECURE_MODE ? 80 : 443, }); - console.log(peerID) + console.log('OpenReplay tracker-assist peerID:', peerID) peer.on('connection', function(conn) { window.addEventListener("beforeunload", () => conn.open && conn.send("unload")); - console.log('connection') + peer.on('error', e => console.log("OpenReplay tracker-assist peer error: ", e.type, e)) + console.log('OpenReplay tracker-assist: Connecting...') conn.on('open', function() { - console.log('connection open') + console.log('OpenReplay tracker-assist: connection opened.') // TODO: onClose const buffer: Message[][] = []; @@ -99,7 +100,7 @@ export default function(opts: Partial = {}) { const confirm = new Confirm(options.confirmText, options.confirmStyle); dataConn.on('data', (data) => { // if call closed by a caller before confirm if (data === "call_end") { - console.log('receiving callend onconfirm') + //console.log('OpenReplay tracker-assist: receiving callend onconfirm') calling = CallingState.False; confirm.remove(); } @@ -118,14 +119,14 @@ export default function(opts: Partial = {}) { const onCallConnect = lStream => { const onCallEnd = () => { - console.log("on callend", call.open) + //console.log("on callend", call.open) mouse.remove(); callUI?.remove(); lStream.getTracks().forEach(t => t.stop()); calling = CallingState.False; } const initiateCallEnd = () => { - console.log("callend initiated") + //console.log("callend initiated") call.close() notifyCallEnd(); onCallEnd(); @@ -165,12 +166,12 @@ export default function(opts: Partial = {}) { callUI.setRemoteStream(rStream); dataConn.on('data', (data: any) => { if (data === "call_end") { - console.log('receiving callend on call') + //console.log('receiving callend on call') onCallEnd(); return; } if (data && typeof data.name === 'string') { - console.log("name",data) + //console.log("name",data) callUI.setAssistentName(data.name); } if (data && typeof data.x === 'number' && typeof data.y === 'number') { @@ -182,9 +183,10 @@ export default function(opts: Partial = {}) { navigator.mediaDevices.getUserMedia({video:true, audio:true}) .then(onCallConnect) - .catch(e => { // TODO retry only if specific error + .catch(_ => { // TODO retry only if specific error navigator.mediaDevices.getUserMedia({audio:true}) // in case there is no camera on device - .then(onCallConnect); + .then(onCallConnect) + .catch(e => console.log("OpenReplay tracker-assist: cant reach media devices. ", e)); }); }); }); From 6e01be8ebd69b7f3048cf35d33e6f5d474dddd2c Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 13 Aug 2021 18:11:37 +0200 Subject: [PATCH 09/12] feat(utilities): log peerJS-server errors (#154) --- utilities/server.js | 3 ++- utilities/servers/peerjs-server.js | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/utilities/server.js b/utilities/server.js index 81a751d03..1b742bbea 100644 --- a/utilities/server.js +++ b/utilities/server.js @@ -1,5 +1,5 @@ var sourcemapsReaderServer = require('./servers/sourcemaps-server'); -var {peerRouter, peerConnection, peerDisconnect} = require('./servers/peerjs-server'); +var {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server'); var express = require('express'); const {ExpressPeerServer} = require('peer'); @@ -28,6 +28,7 @@ const peerServer = ExpressPeerServer(server, { }); peerServer.on('connection', peerConnection); peerServer.on('disconnect', peerDisconnect); +peerServer.on('error', peerError); app.use('/', peerServer); app.enable('trust proxy'); module.exports = server; diff --git a/utilities/servers/peerjs-server.js b/utilities/servers/peerjs-server.js index 20c18f998..ef6556531 100644 --- a/utilities/servers/peerjs-server.js +++ b/utilities/servers/peerjs-server.js @@ -42,6 +42,11 @@ const peerDisconnect = (client) => { } } +const peerError = (error) => { + console.error('error fired'); + console.error(error); +} + peerRouter.get('/peers', function (req, res) { console.log("looking for all available sessions"); @@ -60,5 +65,6 @@ peerRouter.get('/peers/:projectKey', function (req, res) { module.exports = { peerRouter, peerConnection, - peerDisconnect + peerDisconnect, + peerError }; \ No newline at end of file From 8aa4e81297e6b7e1abcaa3729857cde0578d7c5e Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 13 Aug 2021 18:23:47 +0200 Subject: [PATCH 10/12] feat(api): wrap funnels response in data (#155) --- api/chalicelib/blueprints/bp_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/chalicelib/blueprints/bp_core.py b/api/chalicelib/blueprints/bp_core.py index e2ef487ed..18773f68c 100644 --- a/api/chalicelib/blueprints/bp_core.py +++ b/api/chalicelib/blueprints/bp_core.py @@ -822,7 +822,7 @@ def get_funnel(projectId, funnelId, context): project_id=projectId) if data is None: return {"errors": ["funnel not found"]} - return data + return {"data": data} @app.route('/{projectId}/funnels/{funnelId}', methods=['POST', 'PUT']) From 5645732e61cc97ff966e9e0a7873ec70571ff3a0 Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Fri, 13 Aug 2021 18:40:58 +0200 Subject: [PATCH 11/12] feat(api): assist sort sessions (#157) --- api/chalicelib/core/assist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 18978e159..4b57145e8 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -51,6 +51,7 @@ def get_live_sessions(project_id, filters=None): SELECT {SESSION_PROJECTION_COLS}, %(project_key)s||'-'|| session_id AS peer_id FROM public.sessions AS s WHERE {" AND ".join(extra_constraints)} + ORDER BY start_ts DESC LIMIT 500;""", {"project_id": project_id, "connected_peers": connected_peers, From ddedcf71722d10aae92e61d2864a1daee56e8d3c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 Aug 2021 22:51:14 +0530 Subject: [PATCH 12/12] fix(ui) - sessions events overlapping on filter --- .../Session_/EventsBlock/EventGroupWrapper.js | 2 +- .../Session_/EventsBlock/EventsBlock.js | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index 2950a728e..d065b4d7f 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -17,7 +17,7 @@ class EventGroupWrapper extends React.PureComponent { } componentDidUpdate(prevProps) { - if (prevProps.showLoadInfo !== this.props.showLoadInfo) { + if (prevProps.showLoadInfo !== this.props.showLoadInfo || prevProps.query !== this.props.query) { this.props.mesureHeight(); } } diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index 807607a55..53a83aaa9 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -39,14 +39,11 @@ export default class EventsBlock extends React.PureComponent { write = ({ target: { value, name } }) => { const { filter } = this.state; this.setState({ query: value }) - this.props.setEventFilter({ query: value, filter }) + this.props.setEventFilter({ query: value, filter }) setTimeout(() => { - this.scroller.current.scrollToRow(0); - this.scroller.current.recomputeGridSize(); - this.scroller.current.recomputeRowHeights(); - this.scroller.current.forceUpdateGrid(); - }, 200) + this.scroller.current.scrollToRow(0); + }, 100) } clearSearch = () => { @@ -131,7 +128,8 @@ export default class EventsBlock extends React.PureComponent { playing, eventsIndex, filteredEvents - } = this.props; + } = this.props; + const { query } = this.state; const _events = filteredEvents || events; const isLastEvent = index === _events.size - 1; const isLastInGroup = isLastEvent || _events.get(index + 1).type === TYPES.LOCATION; @@ -148,7 +146,8 @@ export default class EventsBlock extends React.PureComponent { > {({measure, registerChild}) => (
-