Skip to content

Commit

Permalink
Selected Row Visibility
Browse files Browse the repository at this point in the history
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 <william.yang@ericsson.com>
  • Loading branch information
williamsyang-work authored and bhufmann committed Jul 27, 2022
1 parent 0e186e0 commit c558e0d
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 14 deletions.
23 changes: 23 additions & 0 deletions packages/base/src/utils/convert-color-string-to-hex.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[];
Expand Down Expand Up @@ -78,6 +80,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
markerCategoryEntries: [],
markerLayerData: undefined,
collapsedNodes: validateNumArray(this.props.persistChartState?.collapsedNodes) ? this.props.persistChartState.collapsedNodes as number[] : [],
selectedRow: undefined,
columns: [],
collapsedMarkerNodes: validateNumArray(this.props.persistChartState?.collapsedMarkerNodes) ? this.props.persistChartState.collapsedMarkerNodes as number[] : [],
optionsDropdownOpen: false,
Expand Down Expand Up @@ -119,6 +122,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
this.arrowLayer = new TimeGraphChartArrows('timeGraphChartArrows', this.rowController);
this.vscrollLayer = new TimeGraphVerticalScrollbar('timeGraphVerticalScrollbar', this.rowController);
this.chartCursors = new TimeGraphChartCursors('chart-cursors', this.chartLayer, this.rowController, { color: this.props.style.cursorColor });
this.rowController.onSelectedRowChangedHandler(this.onSelectionChange);
this.rowController.onVerticalOffsetChangedHandler(() => {
if (this.timeGraphTreeRef.current) {
this.timeGraphTreeRef.current.scrollTop = this.rowController.verticalOffset;
Expand Down Expand Up @@ -155,7 +159,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}
}
});
signalManager().on(Signals.SELECTION_CHANGED, this.onSelectionChanged);

}

synchronizeTreeScroll(): void {
Expand All @@ -169,10 +173,21 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
styleModel: await this.styleProvider.getStyleModel()
});
this.waitAnalysisCompletion();
this.subscribeToEvents();
}

componentWillUnmount(): void {
super.componentWillUnmount();
this.unsubscribeToEvents();
}

protected subscribeToEvents(): void {
signalManager().on(Signals.THEME_CHANGED, this.onThemeChange);
signalManager().on(Signals.SELECTION_CHANGED, this.onSelectionChanged);
}

protected unsubscribeToEvents(): void {
signalManager().off(Signals.THEME_CHANGED, this.onThemeChange);
signalManager().off(Signals.SELECTION_CHANGED, this.onSelectionChanged);
}

Expand Down Expand Up @@ -366,8 +381,10 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
entries={this.state.timegraphTree}
showCheckboxes={false}
onToggleCollapse={this.onToggleCollapse}
onRowClick={this.onRowClick}
selectedRow={this.state.selectedRow}
showHeader={false}
className="table-tree timegraph-tree"
className='table-tree timegraph-tree'
/>
</div>
<div ref={this.markerTreeRef} className='scrollable'
Expand All @@ -381,7 +398,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
onToggleCollapse={this.onToggleAnnotationCollapse}
onClose={this.onMarkerCategoryRowClose}
showHeader={false}
className="table-tree timegraph-tree"
className='table-tree timegraph-tree'
/>
</div>
</>;
Expand Down Expand Up @@ -708,14 +725,32 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}

private getRowStyle(row?: TimelineChart.TimeGraphRowModel) {

let backgroundColor = 0x979797;

if (row?.selected) {
const colorString = getComputedStyle(document.body).getPropertyValue('--theia-selection-background');
const colorNumber = convertColorStringToHexNumber(colorString);
backgroundColor = colorNumber > 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;
Expand Down Expand Up @@ -898,6 +933,24 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}
}

/**
* This method is passed down all the way to TableCell component.
* It communicates a row-selection with the timeline-chart component.
* @param {number} id TreeNode id number
*/
public onRowClick = (id: number): void => {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,7 +37,8 @@ export class EntryTree extends React.Component<EntryTreeProps> {
}

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 <FilterTree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { TableRow } from './table-row';

interface TableBodyProps {
nodes: TreeNode[];
selectedNode?: number;
collapsedNodes: number[];
isCheckable: boolean;
isClosable: boolean;
getCheckedStatus: (id: number) => number;
onToggleCollapse: (id: number) => void;
onRowClick: (id: number) => void;
onClose: (id: number) => void;
onToggleCheck: (id: number) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@ import { TreeNode } from './tree-node';
interface TableCellProps {
node: TreeNode;
index: number;
onRowClick: (id: number) => void;
selectedRow?: number;
}

export class TableCell extends React.Component<TableCellProps> {
constructor(props: TableCellProps) {
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 (
<td key={this.props.index+'-td-'+this.props.node.id}>
<td key={this.props.index+'-td-'+this.props.node.id} onClick={this.onClick} className={className}>
<span>
{this.props.children}
{content}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import icons from './icons';
interface TableRowProps {
node: TreeNode;
level: number;
selectedRow?: number;
collapsedNodes: number[];
isCheckable: boolean;
isClosable: boolean;
getCheckedStatus: (id: number) => number;
onToggleCollapse: (id: number) => void;
onClose: (id: number) => void;
onToggleCheck: (id: number) => void;
onRowClick: (id: number) => void;
}

export class TableRow extends React.Component<TableRowProps> {
Expand Down Expand Up @@ -59,14 +61,21 @@ export class TableRow extends React.Component<TableRowProps> {
: undefined;

renderRow = (): React.ReactNode => {
const row = this.props.node.labels.map((_label: string, index) =>
<TableCell key={this.props.node.id + '-' + index} index={index} node={this.props.node}>
const { node, onRowClick, selectedRow } = this.props;
const row = node.labels.map((_label: string, index) =>
<TableCell
key={node.id + '-' + index}
index={index}
node={node}
onRowClick={onRowClick}
selectedRow={selectedRow}
>
{ (index === 0) ? this.renderToggleCollapse() : undefined }
{ (index === 0) ? this.renderCheckbox() : undefined }
{ (index === 0) ? this.renderCloseButton() : undefined }
</TableCell>
);
row.push(<td key={this.props.node.id + '-filler'} className='filler'/>);
row.push(<td key={node.id + '-filler'} className='filler'/>);
return row;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -253,19 +255,22 @@ export class FilterTree extends React.Component<FilterTreeProps, FilterTreeState
renderTable = (nodes: TreeNode[]): JSX.Element =>
<Table
nodes={nodes}
selectedRow={this.props.selectedRow}
collapsedNodes={this.props.collapsedNodes}
isCheckable={this.props.showCheckboxes}
isClosable={this.props.showCloseIcons}
sortConfig={this.state.sortConfig}
getCheckedStatus={this.getCheckedStatus}
onToggleCollapse={this.handleCollapse}
onToggleCheck={this.handleCheck}
onRowClick={this.props.onRowClick}
onClose={this.handleClose}
onSort={this.handleOrderChange}
onSortConfigChange={this.handleSortConfigChange}
showHeader={this.props.showHeader}
headers={this.props.headers}
className={this.props.className} />;
className={this.props.className}
/>;

render(): JSX.Element | undefined {
if (!this.props.nodes) { return undefined; }
Expand All @@ -276,7 +281,6 @@ export class FilterTree extends React.Component<FilterTreeProps, FilterTreeState
? this.renderFilterTree()
: this.renderTable(rootNodes)
}

</React.Fragment>;
} else {
return <Message></Message>;
Expand Down
6 changes: 5 additions & 1 deletion packages/react-components/style/output-components-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -259,6 +259,10 @@ canvas {
display: block;
}

.table-tree td.selected {
background-color: var(--theia-selection-background) !important;
}

.resize-handle {
float: right;
position: absolute;
Expand Down

0 comments on commit c558e0d

Please sign in to comment.