Skip to content

Commit

Permalink
Use filter table tree for Available Views view implementation
Browse files Browse the repository at this point in the history
- Use FilterTree
- Add tooltips to TreeNode and TableCell
- Enable sorting and filter

fixes #1113

Signed-off-by: Bernd Hufmann <[email protected]>
  • Loading branch information
bhufmann committed Oct 10, 2024
1 parent 7d8c932 commit 7c98af8
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ export class TableCell extends React.Component<TableCellProps> {
content = node.labels[index];
}

const title = node.showTooltip ? node.labels[index] : undefined;
let title = undefined;

if (node.showTooltip) {
if (node.tooltips !== undefined) {
title = node.tooltips[index];
} else {
title = node.labels[index];
}
}
return (
<td key={this.props.index + '-td-' + this.props.node.id}>
<span title={title}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface TreeNode {
id: number;
parentId: number;
labels: string[];
tooltips?: string[];
children: Array<TreeNode>;
isRoot: boolean;
showTooltip?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { OutputDescriptor } from 'tsp-typescript-client/lib/models/output-descri
import { Experiment } from 'tsp-typescript-client/lib/models/experiment';
import { ITspClientProvider } from 'traceviewer-base/lib/tsp-client-provider';
import { ExperimentManager } from 'traceviewer-base/lib/experiment-manager';
import { AvailableViewsComponent } from '../components/utils/available-views-component';
import { FilterTree } from '../components/utils/filter-tree/tree';
import { TreeNode } from '../components/utils/filter-tree/tree-node';
import { getAllExpandedNodeIds } from '../components/utils/filter-tree/utils';

export interface ReactAvailableViewsProps {
id: string;
Expand All @@ -15,13 +17,18 @@ export interface ReactAvailableViewsProps {
}

export interface ReactAvailableViewsState {
availableOutputDescriptors: OutputDescriptor[];
treeNodes: TreeNode[];
collapsedNodes: number[];
orderedNodes: number[];
selectedOutput: number;
}

export class ReactAvailableViewsWidget extends React.Component<ReactAvailableViewsProps, ReactAvailableViewsState> {
private _selectedExperiment: Experiment | undefined;
private _experimentManager: ExperimentManager;

private _nodeIdToOutput: { [key: number]: OutputDescriptor } = {};

private _onExperimentSelected = (experiment: Experiment): void => this.doHandleExperimentSelectedSignal(experiment);
private _onExperimentClosed = (experiment: Experiment): void => this.doHandleExperimentClosedSignal(experiment);

Expand All @@ -33,7 +40,10 @@ export class ReactAvailableViewsWidget extends React.Component<ReactAvailableVie
});
signalManager().on(Signals.EXPERIMENT_SELECTED, this._onExperimentSelected);
signalManager().on(Signals.EXPERIMENT_CLOSED, this._onExperimentClosed);
this.state = { availableOutputDescriptors: [] };
this._nodeIdToOutput = {};
this.state = { treeNodes: [], collapsedNodes: [], orderedNodes: [], selectedOutput: -1 };
this.onToggleCollapse = this.onToggleCollapse.bind(this);
this.onOrderChange = this.onOrderChange.bind(this);
}

componentWillUnmount(): void {
Expand All @@ -44,54 +54,77 @@ export class ReactAvailableViewsWidget extends React.Component<ReactAvailableVie
render(): React.ReactNode {
return (
<div className="trace-explorer-views">
<AvailableViewsComponent
traceID={this._selectedExperiment?.UUID}
outputDescriptors={this.state.availableOutputDescriptors}
onContextMenuEvent={this.handleContextMenuEvent}
onOutputClicked={this.handleOutputClicked}
highlightAfterSelection={true}
></AvailableViewsComponent>
<div className="trace-explorer-panel-content">{this.renderOutputs()}</div>
</div>
);
}

protected handleOutputClicked = (outputDescriptor: OutputDescriptor): void =>
this.doHandleOutputClicked(outputDescriptor);
protected handleContextMenuEvent = (
e: React.MouseEvent<HTMLDivElement>,
output: OutputDescriptor | undefined
): void => this.doHandleContextMenuEvent(e, output);
private renderOutputs() {
if (this.state.treeNodes) {
return (
<div className="scrollable avail-views-table">
<FilterTree
collapsedNodes={this.state.collapsedNodes}
className="table-tree"
nodes={this.state.treeNodes}
onToggleCollapse={this.onToggleCollapse}
onOrderChange={this.onOrderChange}
selectedRow={this.state.selectedOutput}
showCheckboxes={false}
showFilter={true}
showHeader={true}
hideFillers={true}
onRowClick={id => {
this.handleOutputClicked(id);
}}
onContextMenu={this.handleContextMenuEvent}
headers={[{ title: 'Name', sortable: true, resizable: true }]}
/>
</div>
);
}
return <></>;
}
protected handleOutputClicked = (id: number): void => this.doHandleOutputClicked(id);
protected handleContextMenuEvent = (e: React.MouseEvent<HTMLDivElement>, id: number | undefined): void =>
this.doHandleContextMenuEvent(e, id);

private doHandleOutputClicked(selectedOutput: OutputDescriptor) {
private doHandleOutputClicked(id: number) {
const selectedOutput: OutputDescriptor = this._nodeIdToOutput[id];
this.setState({ selectedOutput: id });
if (selectedOutput && this._selectedExperiment) {
signalManager().fireOutputAddedSignal(
new OutputAddedSignalPayload(selectedOutput, this._selectedExperiment)
);
if (selectedOutput.type !== 'NONE') {
signalManager().fireOutputAddedSignal(
new OutputAddedSignalPayload(selectedOutput, this._selectedExperiment)
);
}
}
}

protected doHandleContextMenuEvent(
event: React.MouseEvent<HTMLDivElement>,
output: OutputDescriptor | undefined
): void {
if (this.props.contextMenuRenderer && output) {
this.props.contextMenuRenderer(event, output);
protected doHandleContextMenuEvent(event: React.MouseEvent<HTMLDivElement>, id: number | undefined): void {
if (id !== undefined) {
const output: OutputDescriptor = this._nodeIdToOutput[id];
if (this.props.contextMenuRenderer && output) {
this.props.contextMenuRenderer(event, output);
}
}
event.preventDefault();
event.stopPropagation();
}

protected doHandleExperimentSelectedSignal(experiment: Experiment | undefined): void {
if (this._selectedExperiment?.UUID !== experiment?.UUID || this.state.availableOutputDescriptors.length === 0) {
if (this._selectedExperiment?.UUID !== experiment?.UUID || this.state.treeNodes.length === 0) {
this._selectedExperiment = experiment;
this.setState({ availableOutputDescriptors: [] });
this._nodeIdToOutput = {};
this.setState({ treeNodes: [] });
this.updateAvailableViews();
}
}

protected doHandleExperimentClosedSignal(experiment: Experiment | undefined): void {
if (this._selectedExperiment?.UUID === experiment?.UUID) {
this.setState({ availableOutputDescriptors: [] });
this._nodeIdToOutput = {};
this.setState({ treeNodes: [] });
}
}

Expand All @@ -102,9 +135,24 @@ export class ReactAvailableViewsWidget extends React.Component<ReactAvailableVie
const signalExperiment: Experiment | undefined = this._selectedExperiment;
if (signalExperiment) {
outputs = await this.getOutputDescriptors(signalExperiment);
this.setState({ availableOutputDescriptors: outputs });
const entries: TreeNode[] = [];
outputs.forEach((output, index) => {
const node: TreeNode = {
id: index,
parentId: -1,
labels: [output?.name],
tooltips: [output?.description],
children: [],
isRoot: true,
showTooltip: true
};
entries.push(node);
this._nodeIdToOutput[index] = output;
});
this.setState({ treeNodes: entries });
} else {
this.setState({ availableOutputDescriptors: [] });
this._nodeIdToOutput = {};
this.setState({ treeNodes: [] });
}
}

Expand All @@ -117,4 +165,22 @@ export class ReactAvailableViewsWidget extends React.Component<ReactAvailableVie

return outputDescriptors;
}

private onToggleCollapse(id: number, nodes: TreeNode[]) {
let newList = [...this.state.collapsedNodes];

const exist = this.state.collapsedNodes.find(expandId => expandId === id);

if (exist !== undefined) {
newList = newList.filter(collapsed => id !== collapsed);
} else {
newList = newList.concat(id);
}
const orderedIds = getAllExpandedNodeIds(nodes, newList);
this.setState({ collapsedNodes: newList, orderedNodes: orderedIds });
}

protected onOrderChange(ids: number[]): void {
this.setState({ orderedNodes: ids });
}
}
9 changes: 9 additions & 0 deletions packages/react-components/style/output-components-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@ canvas {
font-weight: normal;
}

.avail-views-table .table-tree td span {
text-wrap: pretty;
min-width: 100px;
}

.avail-views-table .table-tree th {
font-weight: normal;
}

.table-tree tr.selected td {
background-color: var(--trace-viewer-selection-background) !important;
}
Expand Down

0 comments on commit 7c98af8

Please sign in to comment.