Skip to content

Commit 6f6d7d5

Browse files
authored
Merge pull request #62 from patternfly/fix-overlap-bug
fix(LogViewer): Prevent long lines from overlapping the line after them
2 parents 3aedede + 7b7e36e commit 6f6d7d5

File tree

5 files changed

+53
-3
lines changed

5 files changed

+53
-3
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
],
1010
"scripts": {
1111
"build": "yarn workspace @patternfly/react-log-viewer build",
12+
"build:watch": "npm run build:watch -w @patternfly/react-log-viewer",
1213
"build:docs": "yarn workspace @patternfly/react-log-viewer docs:build",
13-
"start": "yarn build && concurrently --kill-others \"yarn workspace @patternfly/react-log-viewer docs:develop\"",
14+
"start": "concurrently --kill-others \"npm run build:watch\" \"npm run docs:develop -w @patternfly/react-log-viewer\"",
1415
"serve:docs": "yarn workspace @patternfly/react-log-viewer docs:serve",
1516
"clean": "yarn workspace @patternfly/react-log-viewer clean",
1617
"lint:js": "node --max-old-space-size=4096 node_modules/.bin/eslint packages --ext js,jsx,ts,tsx --cache",

packages/module/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"scripts": {
1212
"build": "yarn build:esm && yarn build:cjs",
13+
"build:watch": "npm run build:esm -- --watch",
1314
"build:esm": "tsc --build --verbose ./tsconfig.json",
1415
"build:cjs": "tsc --build --verbose ./tsconfig.cjs.json",
1516
"clean": "rimraf dist",

packages/module/patternfly-docs/content/extensions/react-log-viewer/examples/realTestData.js

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/module/patternfly-docs/generated/extensions/log-viewer/react.js

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const pageData = {
2626
"type": "string | string[]",
2727
"description": "String or String Array data being sent by the consumer"
2828
},
29+
{
30+
"name": "fastRowHeightEstimationLimit",
31+
"type": "number",
32+
"description": "The maximum char length for fast row height estimation.\nFor wrapped lines in Chrome based browsers, lines over this length will actually be rendered to the dom and\nmeasured to prevent a bug where one line can overlap another."
33+
},
2934
{
3035
"name": "footer",
3136
"type": "React.ReactNode",
@@ -101,6 +106,11 @@ const pageData = {
101106
"type": "React.ReactNode",
102107
"description": "Toolbar rendered in the log viewer header"
103108
},
109+
{
110+
"name": "useAnsiClasses",
111+
"type": "boolean",
112+
"description": "Flag to enable or disable the use of classes (instead of inline styles) for ANSI coloring/formatting."
113+
},
104114
{
105115
"name": "width",
106116
"type": "number | string",

packages/module/src/LogViewer/LogViewer.tsx

+38-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ interface LogViewerProps {
5959
innerRef?: React.RefObject<any>;
6060
/** Flag to enable or disable the use of classes (instead of inline styles) for ANSI coloring/formatting. */
6161
useAnsiClasses?: boolean;
62+
/** The maximum char length for fast row height estimation.
63+
* For wrapped lines in Chrome based browsers, lines over this length will actually be rendered to the dom and
64+
* measured to prevent a bug where one line can overlap another.
65+
*/
66+
fastRowHeightEstimationLimit?: number;
6267
}
6368

6469
let canvas: HTMLCanvasElement | undefined;
@@ -92,6 +97,7 @@ const LogViewerBase: React.FunctionComponent<LogViewerProps> = memo(
9297
isTextWrapped = true,
9398
initialIndexWidth,
9499
useAnsiClasses,
100+
fastRowHeightEstimationLimit = 5000,
95101
...props
96102
}: LogViewerProps) => {
97103
const [searchedInput, setSearchedInput] = useState<string | null>('');
@@ -108,6 +114,8 @@ const LogViewerBase: React.FunctionComponent<LogViewerProps> = memo(
108114
/* Parse data every time it changes */
109115
const parsedData = React.useMemo(() => parseConsoleOutput(data), [data]);
110116

117+
const isChrome = React.useMemo(() => navigator.userAgent.indexOf('Chrome') !== -1, []);
118+
111119
const ansiUp = new AnsiUp();
112120
// eslint-disable-next-line camelcase
113121
ansiUp.escape_html = false;
@@ -228,6 +236,26 @@ const LogViewerBase: React.FunctionComponent<LogViewerProps> = memo(
228236
setListKey(listKey => listKey + 1);
229237
}, [isTextWrapped]);
230238

239+
const computeRowHeight = (rowText: string, estimatedHeight: number) => {
240+
const logViewerList = containerRef.current.firstChild.firstChild;
241+
242+
// early return with the estimated height if the log viewer list hasn't been rendered yet,
243+
// this will be called again once it has been rendered and the correct height will be set
244+
if (!logViewerList) {
245+
return estimatedHeight;
246+
}
247+
248+
const dummyText = document.createElement('span');
249+
dummyText.className = css(styles.logViewerText);
250+
dummyText.innerHTML = rowText;
251+
252+
logViewerList.appendChild(dummyText);
253+
const computedHeight = dummyText.clientHeight
254+
logViewerList.removeChild(dummyText);
255+
256+
return computedHeight
257+
}
258+
231259
const guessRowHeight = (rowIndex: number) => {
232260
if (!isTextWrapped) {
233261
return lineHeight;
@@ -237,7 +265,16 @@ const LogViewerBase: React.FunctionComponent<LogViewerProps> = memo(
237265
// get the row numbers of the current text
238266
const numRows = Math.ceil(rowText.length / charNumsPerLine);
239267
// multiply by line height to get the total height
240-
return lineHeight * (numRows || 1);
268+
const heightGuess = lineHeight * (numRows || 1);
269+
270+
// because of a bug in react-window (which seems to be limited to chrome) we need to
271+
// actually compute row height in long lines to prevent them from overflowing.
272+
// related issue https://github.com/bvaughn/react-window/issues/593
273+
if (rowText.length > fastRowHeightEstimationLimit && isChrome && isTextWrapped) {
274+
return computeRowHeight(rowText, heightGuess);
275+
}
276+
277+
return heightGuess
241278
};
242279

243280
const createList = (parsedData: string[]) => (

0 commit comments

Comments
 (0)