From e21c98d910bf2f77031b8c0cbfa15c18017b204d Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Thu, 10 Oct 2024 10:21:20 -0400 Subject: [PATCH] Use filter table tree for Available Views view implementation - Use FilterTree - Add tooltips to TreeNode and TableCell - Enable sorting and filter fixes #1113 Signed-off-by: Bernd Hufmann --- .../utils/filter-tree/table-cell.tsx | 9 +- .../utils/filter-tree/tree-node.tsx | 1 + .../trace-explorer-views-widget.tsx | 126 +++++++++++++----- .../style/output-components-style.css | 11 ++ 4 files changed, 115 insertions(+), 32 deletions(-) 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 792e4cab7..aec2df472 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 @@ -21,8 +21,15 @@ export class TableCell extends React.Component { 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 ( diff --git a/packages/react-components/src/components/utils/filter-tree/tree-node.tsx b/packages/react-components/src/components/utils/filter-tree/tree-node.tsx index 10e590a54..a041faae8 100644 --- a/packages/react-components/src/components/utils/filter-tree/tree-node.tsx +++ b/packages/react-components/src/components/utils/filter-tree/tree-node.tsx @@ -2,6 +2,7 @@ export interface TreeNode { id: number; parentId: number; labels: string[]; + tooltips?: string[]; children: Array; isRoot: boolean; showTooltip?: boolean; diff --git a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx index b2eac9058..3548e0ebc 100644 --- a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx +++ b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx @@ -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; @@ -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 { 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); @@ -33,7 +40,10 @@ export class ReactAvailableViewsWidget extends React.Component - +
{this.renderOutputs()}
); } - protected handleOutputClicked = (outputDescriptor: OutputDescriptor): void => - this.doHandleOutputClicked(outputDescriptor); - protected handleContextMenuEvent = ( - e: React.MouseEvent, - output: OutputDescriptor | undefined - ): void => this.doHandleContextMenuEvent(e, output); + private renderOutputs() { + if (this.state.treeNodes) { + return ( + { + this.handleOutputClicked(id); + }} + onContextMenu={this.handleContextMenuEvent} + headers={[{ title: 'Name', sortable: true, resizable: true }]} + /> + ); + } + return <>; + } + protected handleOutputClicked = (id: number): void => this.doHandleOutputClicked(id); + protected handleContextMenuEvent = (e: React.MouseEvent, 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, - output: OutputDescriptor | undefined - ): void { - if (this.props.contextMenuRenderer && output) { - this.props.contextMenuRenderer(event, output); + protected doHandleContextMenuEvent(event: React.MouseEvent, 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: [] }); } } @@ -102,9 +133,24 @@ export class ReactAvailableViewsWidget extends React.Component { + 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: [] }); } } @@ -117,4 +163,22 @@ export class ReactAvailableViewsWidget extends React.Component 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 }); + } + + private onOrderChange(ids: number[]): void { + this.setState({ orderedNodes: ids }); + } } diff --git a/packages/react-components/style/output-components-style.css b/packages/react-components/style/output-components-style.css index 7fc38bd58..e79e02b47 100644 --- a/packages/react-components/style/output-components-style.css +++ b/packages/react-components/style/output-components-style.css @@ -258,6 +258,17 @@ canvas { font-weight: normal; } +.avail-views-table { + border: 0px; + width: 100%; +} + +.avail-views-table td { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + .table-tree tr.selected td { background-color: var(--trace-viewer-selection-background) !important; }