From 12f71925c26fa92c575472f804cfc85187ac0b6a Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Tue, 3 Nov 2020 16:50:39 -0300 Subject: [PATCH] Multiselect dropdown slowness (fix) (#5221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * created util to estimate reasonable width for dropdown * removed unused import * improved calculation of item percentile * added getItemOfPercentileLength to relevant spots * added getItemOfPercentileLength to relevant spots * Added missing import * created custom select element * added check for property path * removed uses of percentile util * gave up on getting element reference * finished testing Select component * removed unused imports * removed older uses of Option component * added canvas calculation * removed minWidth from Select * improved calculation * added fallbacks * added estimated offset * removed leftovers 😅 * replaced to percentiles to max value * switched to memo and renamed component * proper useMemo syntax * Update client/app/components/Select.tsx Co-authored-by: Gabriel Dutra * created custom restrictive types * added quick const * fixed style * fixed generics * added pos absolute to fix percy * removed custom select from ParameterMappingInput * applied prettier * Revert "added pos absolute to fix percy" This reverts commit 4daf1d4bef9edf93cd9bb1f404bd022472ff17a2. * Pin Percy version to 0.24.3 * Update client/app/components/ParameterMappingInput.jsx Co-authored-by: Gabriel Dutra * renamed Select.jsx to SelectWithVirtualScroll Co-authored-by: Gabriel Dutra --- .../app/components/ParameterMappingInput.jsx | 16 +--- client/app/components/ParameterValueInput.jsx | 21 ++--- .../components/QueryBasedParameterInput.jsx | 17 ++-- .../components/SelectWithVirtualScroll.tsx | 38 +++++++++ client/app/lib/calculateTextWidth.ts | 20 +++++ package-lock.json | 85 ++++++++++++------- package.json | 4 +- 7 files changed, 128 insertions(+), 73 deletions(-) create mode 100644 client/app/components/SelectWithVirtualScroll.tsx create mode 100644 client/app/lib/calculateTextWidth.ts diff --git a/client/app/components/ParameterMappingInput.jsx b/client/app/components/ParameterMappingInput.jsx index dd607fc6..0f186362 100644 --- a/client/app/components/ParameterMappingInput.jsx +++ b/client/app/components/ParameterMappingInput.jsx @@ -25,8 +25,6 @@ import CheckOutlinedIcon from "@ant-design/icons/CheckOutlined"; import "./ParameterMappingInput.less"; -const { Option } = Select; - export const MappingType = { DashboardAddNew: "dashboard-add-new", DashboardMapToExisting: "dashboard-map-to-existing", @@ -208,19 +206,9 @@ export class ParameterMappingInput extends React.Component { renderDashboardMapToExisting() { const { mapping, existingParamNames } = this.props; + const options = map(existingParamNames, paramName => ({ label: paramName, value: paramName })); - return ( - - ); + return ({ label: String(opt), value: opt }))} showSearch showArrow - style={{ minWidth: 60 }} notFoundContent={isEmpty(enumOptionsArray) ? "No options available" : null} - {...multipleValuesProps}> - {enumOptionsArray.map(option => ( - - ))} - + {...multipleValuesProps} + /> ); } diff --git a/client/app/components/QueryBasedParameterInput.jsx b/client/app/components/QueryBasedParameterInput.jsx index 1343a81b..2479d511 100644 --- a/client/app/components/QueryBasedParameterInput.jsx +++ b/client/app/components/QueryBasedParameterInput.jsx @@ -1,9 +1,7 @@ import { find, isArray, get, first, map, intersection, isEqual, isEmpty } from "lodash"; import React from "react"; import PropTypes from "prop-types"; -import Select from "antd/lib/select"; - -const { Option } = Select; +import SelectWithVirtualScroll from "@/components/SelectWithVirtualScroll"; export default class QueryBasedParameterInput extends React.Component { static propTypes = { @@ -83,25 +81,20 @@ export default class QueryBasedParameterInput extends React.Component { const { loading, options } = this.state; return ( - + {...otherProps} + /> ); } diff --git a/client/app/components/SelectWithVirtualScroll.tsx b/client/app/components/SelectWithVirtualScroll.tsx new file mode 100644 index 00000000..1e2df97e --- /dev/null +++ b/client/app/components/SelectWithVirtualScroll.tsx @@ -0,0 +1,38 @@ +import React, { useMemo } from "react"; +import { maxBy } from "lodash"; +import AntdSelect, { SelectProps, LabeledValue } from "antd/lib/select"; +import { calculateTextWidth } from "@/lib/calculateTextWidth"; + +const MIN_LEN_FOR_VIRTUAL_SCROLL = 400; + +interface VirtualScrollLabeledValue extends LabeledValue { + label: string; +} + +interface VirtualScrollSelectProps extends SelectProps { + options: Array; +} +function SelectWithVirtualScroll({ options, ...props }: VirtualScrollSelectProps): JSX.Element { + const dropdownMatchSelectWidth = useMemo(() => { + if (options && options.length > MIN_LEN_FOR_VIRTUAL_SCROLL) { + const largestOpt = maxBy(options, "label.length"); + + if (largestOpt) { + const offset = 40; + const optionText = largestOpt.label; + const width = calculateTextWidth(optionText); + if (width) { + return width + offset; + } + } + + return true; + } + + return false; + }, [options]); + + return dropdownMatchSelectWidth={dropdownMatchSelectWidth} options={options} {...props} />; +} + +export default SelectWithVirtualScroll; diff --git a/client/app/lib/calculateTextWidth.ts b/client/app/lib/calculateTextWidth.ts new file mode 100644 index 00000000..55e7b270 --- /dev/null +++ b/client/app/lib/calculateTextWidth.ts @@ -0,0 +1,20 @@ +const canvas = document.createElement("canvas"); +canvas.style.display = "none"; +document.body.appendChild(canvas); + +export function calculateTextWidth(text: string, container = document.body) { + const ctx = canvas.getContext("2d"); + if (ctx) { + const containerStyle = window.getComputedStyle(container); + ctx.font = `${containerStyle.fontSize} ${containerStyle.fontFamily}`; + const textMetrics = ctx.measureText(text); + let actualWidth = textMetrics.width; + if ("actualBoundingBoxLeft" in textMetrics) { + // only available on evergreen browsers + actualWidth = Math.abs(textMetrics.actualBoundingBoxLeft) + Math.abs(textMetrics.actualBoundingBoxRight); + } + return actualWidth; + } + + return null; +} diff --git a/package-lock.json b/package-lock.json index 3b8bf214..ba65138a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4107,12 +4107,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -4270,9 +4269,9 @@ "dev": true }, "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", "dev": true } } @@ -4297,12 +4296,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -4355,13 +4353,21 @@ "dev": true }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "string-width": { @@ -4668,9 +4674,9 @@ "dev": true }, "@percy/agent": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/@percy/agent/-/agent-0.26.2.tgz", - "integrity": "sha512-egwfhCOZnPDKh67Ldi3jN72fReaa3gU3OiXwhibT9NWkMuKh4O6m1zP55gYt0f8qDc5T9ZVfB0fbvsvJuTqujg==", + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@percy/agent/-/agent-0.24.3.tgz", + "integrity": "sha512-gSx1qqtTMLix/ZzGo9taz02zKR9CGAt5megYW3jPW+DhyTaHSHoQgGcEI41el7T7BqB4FbfS0rkeG2B991mNcw==", "dev": true, "requires": { "@oclif/command": "1.5.19", @@ -4798,13 +4804,30 @@ } }, "@percy/cypress": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@percy/cypress/-/cypress-2.3.1.tgz", - "integrity": "sha512-/kswOdqO/w6q7VLPeZENRd9c8aIb+W6oNHZDNkRZ9eIa1O+iMIqHj2aB+Bcbc6WhtlRZM/65ekrGPR7QyK9Y7A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@percy/cypress/-/cypress-2.3.2.tgz", + "integrity": "sha512-pRBUft9gOM5Jduyu0VKfucHy8QTDTIw8y+Zu7JNrNHWa0JcfOJcpQbhZJ6AGmA5xzad05S6wjQ8CnG8y3iaj/w==", "dev": true, "requires": { "@percy/agent": "~0", - "axios": "^0.19.0" + "axios": "^0.20.0" + }, + "dependencies": { + "axios": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true + } } }, "@plotly/d3-sankey": { @@ -5464,6 +5487,12 @@ "robust-orientation": "^1.1.3" } }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -8262,9 +8291,9 @@ } }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -14155,12 +14184,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true - }, "debug": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", diff --git a/package.json b/package.json index 0625a4b3..8f256629 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,8 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.10.4", "@cypress/code-coverage": "^3.8.1", - "@percy/agent": "^0.26.2", - "@percy/cypress": "^2.3.1", + "@percy/agent": "0.24.3", + "@percy/cypress": "^2.3.2", "@types/classnames": "^2.2.10", "@types/hoist-non-react-statics": "^3.3.1", "@types/lodash": "^4.14.157",