From 84c6faa79a1b8226ad562310f2474531baff2eaa Mon Sep 17 00:00:00 2001 From: Will Yang Date: Wed, 6 Jul 2022 10:26:45 -0500 Subject: [PATCH] Selected Row Visibility Filter-tree and timeline-chart rows are now highlighted when selected. Selecting a row in the timeline-chart selects the row in the filter-tree, and vice-versa. Highlighted color matches the current color theme. Fixes: #772, #774 Signed-off-by: Will Yang --- .../src/utils/convert-color-string-to-hex.ts | 23 +++++++ .../components/timegraph-output-component.tsx | 63 +++++++++++++++++-- .../utils/filter-tree/entry-tree.tsx | 5 +- .../utils/filter-tree/table-body.tsx | 2 + .../utils/filter-tree/table-cell.tsx | 14 ++++- .../utils/filter-tree/table-row.tsx | 15 ++++- .../components/utils/filter-tree/table.tsx | 2 + .../src/components/utils/filter-tree/tree.tsx | 8 ++- .../style/output-components-style.css | 6 +- 9 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 packages/base/src/utils/convert-color-string-to-hex.ts diff --git a/packages/base/src/utils/convert-color-string-to-hex.ts b/packages/base/src/utils/convert-color-string-to-hex.ts new file mode 100644 index 000000000..d91993e72 --- /dev/null +++ b/packages/base/src/utils/convert-color-string-to-hex.ts @@ -0,0 +1,23 @@ +/** + * Converts a string representing a color into a number. Works with both RGB strings and hex number strings. Ignores alpha values. + * @param {string} rgb RGB or hex string for a color. + * @returns {number} Hex number of the input string. Ignores alpha value if present. + */ +export function convertColorStringToHexNumber(rgb: string): number { + let string = '0'; + if (rgb[0] === '#') { + // We are working with hex string. + string = '0x' + rgb.slice(1); + } else if (rgb[0] === 'r') { + // Working with RGB String + const match = rgb.match(/\d+/g); + if (match) { + string = '0x' + match.map(x => { + x = parseInt(x).toString(16); + return (x.length === 1) ? '0' + x : x; + }).join(''); + string = string.slice(0, 8); + } + } + return Number(string); +} diff --git a/packages/react-components/src/components/timegraph-output-component.tsx b/packages/react-components/src/components/timegraph-output-component.tsx index 14eb8bdb2..4f6e35392 100644 --- a/packages/react-components/src/components/timegraph-output-component.tsx +++ b/packages/react-components/src/components/timegraph-output-component.tsx @@ -28,6 +28,7 @@ import ColumnHeader from './utils/filter-tree/column-header'; import { TimeGraphAnnotationComponent } from 'timeline-chart/lib/components/time-graph-annotation'; import { Entry } from 'tsp-typescript-client'; import { isEqual } from 'lodash'; +import { convertColorStringToHexNumber } from 'traceviewer-base/lib/utils/convert-color-string-to-hex'; type TimegraphOutputProps = AbstractOutputProps & { addWidgetResizeHandler: (handler: () => void) => void; @@ -38,6 +39,7 @@ type TimegraphOutputState = AbstractOutputState & { timegraphTree: TimeGraphEntry[]; markerCategoryEntries: Entry[]; markerLayerData: { rows: TimelineChart.TimeGraphRowModel[], range: TimelineChart.TimeGraphRange, resolution: number } | undefined; + selectedRow?: number; collapsedNodes: number[]; collapsedMarkerNodes: number[]; columns: ColumnHeader[]; @@ -77,6 +79,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent { if (this.timeGraphTreeRef.current) { this.timeGraphTreeRef.current.scrollTop = this.rowController.verticalOffset; @@ -155,7 +159,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent
; @@ -717,14 +734,32 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent 0 ? colorNumber : backgroundColor; + } + return { - backgroundColor: 0x979797, - backgroundOpacity: row?.selected ? 0.1 : 0, + backgroundColor, + backgroundOpacity: row?.selected ? 0.5 : 0, lineColor: this.props.backgroundTheme === 'light' ? 0xD3D3D3 : 0x3F4146, lineThickness: 1 }; } + public onThemeChange = (): void => { + // Simulate a click on the selected row when theme changes. + // This changes the color of the selected row to new theme. + const selectedRow = this.state.selectedRow; + if (selectedRow) { + this.onRowClick(selectedRow); + } + }; + private getMarkerStateStyle(state: TimelineChart.TimeGraphState) { if (state.data && state.data.annotation) { const annotation = state.data.annotation; @@ -907,6 +942,24 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent { + const rowIndex = getIndexOfNode(id, listToTree(this.state.timegraphTree, this.state.columns), this.state.collapsedNodes); + this.chartLayer.selectAndReveal(rowIndex); + if (this.rowController.selectedRow?.id !== id) { + // This highlights the left side if the row is loading. + this.setState({ selectedRow: id }); + } + }; + + public onSelectionChange = (row: TimelineChart.TimeGraphRowModel): void => { + this.setState({ selectedRow: row.id }); + }; + private selectAndReveal(item: TimeGraphEntry) { const rowIndex = getIndexOfNode(item.id, listToTree(this.state.timegraphTree, this.state.columns), this.state.collapsedNodes); this.chartLayer.selectAndReveal(rowIndex); diff --git a/packages/react-components/src/components/utils/filter-tree/entry-tree.tsx b/packages/react-components/src/components/utils/filter-tree/entry-tree.tsx index 35639cab0..d161b2d70 100644 --- a/packages/react-components/src/components/utils/filter-tree/entry-tree.tsx +++ b/packages/react-components/src/components/utils/filter-tree/entry-tree.tsx @@ -10,9 +10,11 @@ interface EntryTreeProps { checkedSeries: number[]; showCheckboxes: boolean; showCloseIcons: boolean; + selectedRow?: number; collapsedNodes: number[]; showFilter: boolean; onToggleCheck: (ids: number[]) => void; + onRowClick: (id: number) => void; onClose: (id: number) => void; onToggleCollapse: (id: number, nodes: TreeNode[]) => void; onOrderChange: (ids: number[]) => void; @@ -35,7 +37,8 @@ export class EntryTree extends React.Component { } shouldComponentUpdate = (nextProps: EntryTreeProps): boolean => - (this.props.checkedSeries !== nextProps.checkedSeries || this.props.entries !== nextProps.entries || this.props.collapsedNodes !== nextProps.collapsedNodes); + (this.props.checkedSeries !== nextProps.checkedSeries || this.props.entries !== nextProps.entries + || this.props.collapsedNodes !== nextProps.collapsedNodes || this.props.selectedRow !== nextProps.selectedRow); render(): JSX.Element { return number; onToggleCollapse: (id: number) => void; + onRowClick: (id: number) => void; onClose: (id: number) => void; onToggleCheck: (id: number) => void; } diff --git a/packages/react-components/src/components/utils/filter-tree/table-cell.tsx b/packages/react-components/src/components/utils/filter-tree/table-cell.tsx index 481668fe1..5a76e64dd 100644 --- a/packages/react-components/src/components/utils/filter-tree/table-cell.tsx +++ b/packages/react-components/src/components/utils/filter-tree/table-cell.tsx @@ -4,6 +4,8 @@ import { TreeNode } from './tree-node'; interface TableCellProps { node: TreeNode; index: number; + onRowClick: (id: number) => void; + selectedRow?: number; } export class TableCell extends React.Component { @@ -11,10 +13,18 @@ export class TableCell extends React.Component { super(props); } + private onClick = () => { + const { node, onRowClick } = this.props; + onRowClick(node.id); + }; + render(): React.ReactNode { - const content = this.props.node.labels[this.props.index]; + const { node, selectedRow, index } = this.props; + const content = node.labels[index]; + const className = (selectedRow === node.id) ? 'selected' : ''; + return ( - + {this.props.children} {content} diff --git a/packages/react-components/src/components/utils/filter-tree/table-row.tsx b/packages/react-components/src/components/utils/filter-tree/table-row.tsx index 9827284d4..efc20687a 100644 --- a/packages/react-components/src/components/utils/filter-tree/table-row.tsx +++ b/packages/react-components/src/components/utils/filter-tree/table-row.tsx @@ -7,6 +7,7 @@ import icons from './icons'; interface TableRowProps { node: TreeNode; level: number; + selectedRow?: number; collapsedNodes: number[]; isCheckable: boolean; isClosable: boolean; @@ -14,6 +15,7 @@ interface TableRowProps { onToggleCollapse: (id: number) => void; onClose: (id: number) => void; onToggleCheck: (id: number) => void; + onRowClick: (id: number) => void; } export class TableRow extends React.Component { @@ -59,14 +61,21 @@ export class TableRow extends React.Component { : undefined; renderRow = (): React.ReactNode => { - const row = this.props.node.labels.map((_label: string, index) => - + const { node, onRowClick, selectedRow } = this.props; + const row = node.labels.map((_label: string, index) => + { (index === 0) ? this.renderToggleCollapse() : undefined } { (index === 0) ? this.renderCheckbox() : undefined } { (index === 0) ? this.renderCloseButton() : undefined } ); - row.push(); + row.push(); return row; }; diff --git a/packages/react-components/src/components/utils/filter-tree/table.tsx b/packages/react-components/src/components/utils/filter-tree/table.tsx index e3d798ab7..5025d6b65 100644 --- a/packages/react-components/src/components/utils/filter-tree/table.tsx +++ b/packages/react-components/src/components/utils/filter-tree/table.tsx @@ -7,10 +7,12 @@ import ColumnHeader from './column-header'; interface TableProps { nodes: TreeNode[]; + selectedRow?: number; collapsedNodes: number[]; isCheckable: boolean; isClosable: boolean; sortConfig: SortConfig[]; + onRowClick: (id: number) => void; getCheckedStatus: (id: number) => number; onToggleCollapse: (id: number) => void; onToggleCheck: (id: number) => void; diff --git a/packages/react-components/src/components/utils/filter-tree/tree.tsx b/packages/react-components/src/components/utils/filter-tree/tree.tsx index 572a73b80..691df75c4 100644 --- a/packages/react-components/src/components/utils/filter-tree/tree.tsx +++ b/packages/react-components/src/components/utils/filter-tree/tree.tsx @@ -15,8 +15,10 @@ interface FilterTreeProps { showFilter: boolean; // Optional checkedSeries: number[]; // Optional collapsedNodes: number[]; + selectedRow?: number; onToggleCheck: (ids: number[]) => void; // Optional onClose: (id: number) => void; + onRowClick: (id: number) => void; onToggleCollapse: (id: number, nodes: TreeNode[]) => void; onOrderChange: (ids: number[]) => void; showHeader: boolean; @@ -253,6 +255,7 @@ export class FilterTree extends React.Component ; + className={this.props.className} + />; render(): JSX.Element | undefined { if (!this.props.nodes) { return undefined; } @@ -276,7 +281,6 @@ export class FilterTree extends React.Component; } else { return ; diff --git a/packages/react-components/style/output-components-style.css b/packages/react-components/style/output-components-style.css index 65c7c91c3..0de6dd194 100644 --- a/packages/react-components/style/output-components-style.css +++ b/packages/react-components/style/output-components-style.css @@ -236,7 +236,7 @@ canvas { } .table-tree tr:hover td { - background-color: var(--theia-list-hoverBackground) !important; + background-color: var(--theia-list-hoverBackground); } .table-tree td { @@ -259,6 +259,10 @@ canvas { display: block; } +.table-tree td.selected { + background-color: var(--theia-selection-background) !important; +} + .resize-handle { float: right; position: absolute;