diff --git a/packages/base/src/signals/item-properties-signal-payload.tsx b/packages/base/src/signals/item-properties-signal-payload.tsx new file mode 100644 index 00000000..1ca07f4e --- /dev/null +++ b/packages/base/src/signals/item-properties-signal-payload.tsx @@ -0,0 +1,29 @@ +/*************************************************************************************** + * Copyright (c) 2024 BlackBerry Limited and contributors. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + ***************************************************************************************/ + +export class ItemPropertiesSignalPayload { + private outputDescriptorId: string | undefined; + private experimentUUID: string | undefined; + private properties: { [key: string]: string }; + + constructor(props: { [key: string]: string }, expUUID?: string, descriptorId?: string) { + this.properties = props; + this.experimentUUID = expUUID; + this.outputDescriptorId = descriptorId; + } + + public getOutputDescriptorId(): string | undefined { + return this.outputDescriptorId; + } + + public getExperimentUUID(): string | undefined { + return this.experimentUUID; + } + + public getProperties(): { [key: string]: string } { + return this.properties; + } +} diff --git a/packages/base/src/signals/signal-manager.ts b/packages/base/src/signals/signal-manager.ts index 1ffd2334..6592a4c5 100644 --- a/packages/base/src/signals/signal-manager.ts +++ b/packages/base/src/signals/signal-manager.ts @@ -8,6 +8,7 @@ import { TimeRangeUpdatePayload } from './time-range-data-signal-payloads'; import { ContextMenuContributedSignalPayload } from './context-menu-contributed-signal-payload'; import { ContextMenuItemClickedSignalPayload } from './context-menu-item-clicked-signal-payload'; import { RowSelectionsChangedSignalPayload } from './row-selections-changed-signal-payload'; +import { ItemPropertiesSignalPayload } from './item-properties-signal-payload'; export declare interface SignalManager { fireTraceOpenedSignal(trace: Trace): void; @@ -19,7 +20,7 @@ export declare interface SignalManager { fireExperimentUpdatedSignal(experiment: Experiment): void; fireOpenedTracesChangedSignal(payload: OpenedTracesUpdatedSignalPayload): void; fireOutputAddedSignal(payload: OutputAddedSignalPayload): void; - fireItemPropertiesSignalUpdated(properties?: { [key: string]: string }): void; + fireItemPropertiesSignalUpdated(payload: ItemPropertiesSignalPayload): void; fireThemeChangedSignal(theme: string): void; // TODO - Refactor or remove this signal. Similar signal to fireRequestSelectionRangeChange fireSelectionChangedSignal(payload: { [key: string]: string }): void; @@ -119,8 +120,8 @@ export class SignalManager extends EventEmitter implements SignalManager { fireOutputAddedSignal(payload: OutputAddedSignalPayload): void { this.emit(Signals.OUTPUT_ADDED, payload); } - fireItemPropertiesSignalUpdated(properties?: { [key: string]: string }): void { - this.emit(Signals.ITEM_PROPERTIES_UPDATED, properties); + fireItemPropertiesSignalUpdated(payload: ItemPropertiesSignalPayload): void { + this.emit(Signals.ITEM_PROPERTIES_UPDATED, payload); } fireThemeChangedSignal(theme: string): void { this.emit(Signals.THEME_CHANGED, theme); diff --git a/packages/react-components/src/components/table-output-component.tsx b/packages/react-components/src/components/table-output-component.tsx index df41e4de..72c588fd 100644 --- a/packages/react-components/src/components/table-output-component.tsx +++ b/packages/react-components/src/components/table-output-component.tsx @@ -23,6 +23,7 @@ import { SearchFilterRenderer, CellRenderer, LoadingRenderer } from './table-ren import { OutputDescriptor, ResponseStatus } from 'tsp-typescript-client'; import { PaginationBarComponent } from './utils/pagination-bar-component'; import { OptionCheckBoxState, OptionState, OptionType } from './drop-down-component'; +import { ItemPropertiesSignalPayload } from 'traceviewer-base/lib/signals/item-properties-signal-payload'; type TableOuputState = AbstractOutputState & { tableColumns: ColDef[]; @@ -301,7 +302,9 @@ export class TableOutputComponent extends AbstractOutputComponent void) => void; @@ -929,7 +930,11 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent void; onToggleCheck: (id: number) => void; onContextMenu: (event: React.MouseEvent, id: number) => void; + hideFillers?: boolean; } export class TableBody extends React.Component { 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 84dbb8cb..792e4cab 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 @@ -13,11 +13,19 @@ export class TableCell extends React.Component { } render(): React.ReactNode { const { node, index } = this.props; - const content = node.labels[index]; + + let content; + if (node.elementIndex && node.elementIndex === index && node.getElement) { + content = node.getElement(); + } else { + content = node.labels[index]; + } + + const title = node.showTooltip ? node.labels[index] : undefined; return ( - + {this.props.children} {content} diff --git a/packages/react-components/src/components/utils/filter-tree/table-header.tsx b/packages/react-components/src/components/utils/filter-tree/table-header.tsx index eb25bb9d..27cb084e 100644 --- a/packages/react-components/src/components/utils/filter-tree/table-header.tsx +++ b/packages/react-components/src/components/utils/filter-tree/table-header.tsx @@ -7,6 +7,7 @@ interface TableHeaderProps { sortableColumns: string[]; sortConfig: SortConfig[]; onSort: (sortColumn: string) => void; + hideFillers?: boolean; } export class TableHeader extends React.Component { @@ -18,8 +19,17 @@ export class TableHeader extends React.Component { constructor(props: TableHeaderProps) { super(props); - this.gridTemplateColumns = this.props.columns.map(() => 'max-content'); - this.gridTemplateColumns.push('minmax(0px, 1fr)'); + this.gridTemplateColumns = this.props.columns.map((_header, index) => { + if (index === this.props.columns.length - 1) { + if (this.props.hideFillers) { + return 'auto'; + } + } + return 'max-content'; + }); + if (!this.props.hideFillers) { + this.gridTemplateColumns.push('minmax(0px, 1fr)'); + } } handleSortChange = (sortColumn: string, ev: React.MouseEvent): void => { @@ -106,7 +116,9 @@ export class TableHeader extends React.Component { )); - header.push(); + if (!this.props.hideFillers) { + header.push(); + } return header; }; 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 b58ec262..c46c1ff7 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 @@ -19,6 +19,7 @@ interface TableRowProps { onRowClick: (id: number) => void; onMultipleRowClick?: (id: number, isShiftClicked?: boolean) => void; onContextMenu: (event: React.MouseEvent, id: number) => void; + hideFillers?: boolean; } export class TableRow extends React.Component { @@ -78,7 +79,9 @@ export class TableRow extends React.Component { {index === 0 ? this.renderCloseButton() : undefined} )); - row.push(); + if (!this.props.hideFillers) { + 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 9c891f83..416ef7fa 100644 --- a/packages/react-components/src/components/utils/filter-tree/table.tsx +++ b/packages/react-components/src/components/utils/filter-tree/table.tsx @@ -25,6 +25,7 @@ interface TableProps { showHeader: boolean; headers: ColumnHeader[]; className: string; + hideFillers?: boolean; } export class Table extends React.Component { @@ -59,10 +60,20 @@ export class Table extends React.Component { }; render(): JSX.Element { - const gridTemplateColumns = this.props.headers - .map(() => 'max-content') - .join(' ') - .concat(' minmax(0px, 1fr)'); + let gridTemplateColumns = this.props.headers + .map((_header, index) => { + if (index === this.props.headers.length - 1) { + if (this.props.hideFillers) { + return 'auto'; + } + } + return 'max-content'; + }) + .join(' '); + if (!this.props.hideFillers) { + gridTemplateColumns = gridTemplateColumns.concat(' minmax(0px, 1fr)'); + } + return (
@@ -72,6 +83,7 @@ export class Table extends React.Component { sortableColumns={this.sortableColumns} onSort={this.onSortChange} sortConfig={this.props.sortConfig} + hideFillers={this.props.hideFillers} /> )} 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 7cd98a19..10e590a5 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 @@ -4,4 +4,7 @@ export interface TreeNode { labels: string[]; children: Array; isRoot: boolean; + showTooltip?: boolean; + elementIndex?: number; + getElement?: () => JSX.Element; } 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 cde57407..6fa8b772 100644 --- a/packages/react-components/src/components/utils/filter-tree/tree.tsx +++ b/packages/react-components/src/components/utils/filter-tree/tree.tsx @@ -27,6 +27,7 @@ interface FilterTreeProps { showHeader: boolean; headers: ColumnHeader[]; className: string; + hideFillers?: boolean; } interface FilterTreeState { @@ -288,6 +289,7 @@ export class FilterTree extends React.Component ); diff --git a/packages/react-components/src/trace-explorer/trace-explorer-properties-widget.tsx b/packages/react-components/src/trace-explorer/trace-explorer-properties-widget.tsx index 7cd10927..cbc217e2 100644 --- a/packages/react-components/src/trace-explorer/trace-explorer-properties-widget.tsx +++ b/packages/react-components/src/trace-explorer/trace-explorer-properties-widget.tsx @@ -1,5 +1,9 @@ import * as React from 'react'; +import '../../style/output-components-style.css'; import { signalManager, Signals } from 'traceviewer-base/lib/signals/signal-manager'; +import { FilterTree } from '../components/utils/filter-tree/tree'; +import { TreeNode } from '../components/utils/filter-tree/tree-node'; +import { ItemPropertiesSignalPayload } from 'traceviewer-base/src/signals/item-properties-signal-payload'; export interface ReactPropertiesWidgetProps { id: string; @@ -8,14 +12,14 @@ export interface ReactPropertiesWidgetProps { } export interface ReactPropertiesWidgetState { - itemProperties: { [key: string]: string }; + itemProperties: TreeNode[]; } export class ReactItemPropertiesWidget extends React.Component { constructor(props: ReactPropertiesWidgetProps) { super(props); this.state = { - itemProperties: {} + itemProperties: [] }; signalManager().on(Signals.ITEM_PROPERTIES_UPDATED, this._onItemProperties); } @@ -33,48 +37,72 @@ export class ReactItemPropertiesWidget extends React.Component { - if (key === 'Source') { - const sourceCodeInfo = value; - const matches = sourceCodeInfo.match('(.*):(\\d+)'); - let fileLocation; - let line; - if (matches && matches.length === 3) { - fileLocation = matches[1]; - line = matches[2]; - } - tooltipArray.push( -

- {key + ': ' + sourceCodeInfo} -

- ); - } else { - tooltipArray.push(

{key + ': ' + value}

); - } - }); - } else { - tooltipArray.push( -

- Select item to view properties -

+ return ( +
+ { + // do nothing + }} + headers={[ + { title: 'Key', sortable: true, resizable: true }, + { title: 'Value', resizable: true } + ]} + /> +
); } - - return {tooltipArray.map(element => element)}; + return <>; } /** Tooltip Signal and Signal Handlers */ - protected _onItemProperties = (tooltip: { [key: string]: string }): void => - this.doHandleItemPropertiesSignal(tooltip); + protected _onItemProperties = (data: ItemPropertiesSignalPayload): void => + this.doHandleItemPropertiesSignal(data.getProperties()); private doHandleItemPropertiesSignal(tooltipProps: { [key: string]: string }): void { - this.setState({ itemProperties: tooltipProps }); + const entries: TreeNode[] = []; + Object.entries(tooltipProps).forEach(([key, value], index) => { + const node: TreeNode = { + id: index, + parentId: -1, + labels: [key, value], + children: [], + isRoot: true, + showTooltip: true + }; + if (key === 'Source') { + const sourceCodeInfo = value; + const matches = sourceCodeInfo.match('(.*):(\\d+)'); + let fileLocation: string; + let line: string; + if (matches && matches.length === 3) { + fileLocation = matches[1]; + line = matches[2]; + } + // labels index for which the element needs to be rendered + node.elementIndex = 1; + node.getElement = () => this.getSourceCodeElement(key, value, fileLocation, line); + } + entries.push(node); + }); + this.setState({ itemProperties: entries }); } + + getSourceCodeElement = (key: string, value: string, fileLocation: string, line: string): JSX.Element => ( + + {value} + + ); } diff --git a/packages/react-components/style/output-components-style.css b/packages/react-components/style/output-components-style.css index e917d1d7..f0fe0e7d 100644 --- a/packages/react-components/style/output-components-style.css +++ b/packages/react-components/style/output-components-style.css @@ -16,15 +16,18 @@ grid-template-rows: 25px 1fr; position: relative; } + .widget-handle-with-options { background: var(--trace-viewer-editor-background); display: grid; grid-template-rows: 25px 25px 1fr; position: relative; } + .pinned-view-shadow { box-shadow: 0px 4px 6px 0px var(--trace-viewer-widget-shadow); } + .title-bar-label { text-align: center; writing-mode: vertical-rl; @@ -39,6 +42,7 @@ position: relative; background: inherit; } + .custom-ellipsis { position: absolute; z-index: 1; @@ -46,12 +50,15 @@ height: 15px; bottom: 0; } + .hidden-ellipsis { display: none; } + .title-bar-label:hover { - background-color: var(--trace-viewer-list-hoverBackground); + background-color: var(--trace-viewer-list-hoverBackground); } + .remove-component-button { background: none; border: none; @@ -60,12 +67,14 @@ justify-self: center; color: var(--trace-viewer-ui-font-color0) } + .title-bar-label:hover { - background-color: var(--trace-viewer-list-hoverBackground); + background-color: var(--trace-viewer-list-hoverBackground); } + .remove-component-button:hover { cursor: pointer; - background-color: var(--trace-viewer-list-hoverBackground); + background-color: var(--trace-viewer-list-hoverBackground); } .options-menu-container { @@ -74,14 +83,16 @@ position: relative; display: inline-block; } + .options-menu-button { background: none; border: none; color: var(--trace-viewer-ui-font-color0) } + .options-menu-button:hover { cursor: pointer; - background-color: var(--trace-viewer-list-hoverBackground); + background-color: var(--trace-viewer-list-hoverBackground); } .main-output-container { @@ -93,9 +104,9 @@ } .disable-select { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; user-select: none; } @@ -198,7 +209,9 @@ canvas { border-left: 1px solid var(--trace-viewer-tree-inactiveIndentGuidesStroke); } -.table-tree thead, .table-tree tbody, .table-tree tr { +.table-tree thead, +.table-tree tbody, +.table-tree tr { display: contents; } @@ -235,6 +248,15 @@ canvas { display: block; } +.item-properties-table .table-tree td span { + text-wrap: pretty; + min-width: 100px; +} + +.item-properties-table .table-tree th { + font-weight: normal; +} + .table-tree tr.selected td { background-color: var(--trace-viewer-selection-background) !important; } @@ -267,7 +289,7 @@ canvas { } .timegraph-tree { - border: 0px; + border: 0px; } .timegraph-tree tr { @@ -277,7 +299,7 @@ canvas { .timegraph-tree td { padding: 1px; - border: 0px; + border: 0px; } #input-filter-container { @@ -301,12 +323,12 @@ canvas { color: var(--trace-viewer-input-placeholder-foreground); } -.ag-theme-balham{ +.ag-theme-balham { font-family: var(--trace-viewer-ui-font-family) !important; font-size: var(--trace-viewer-content-font-size) !important; } -.ag-theme-balham-dark{ +.ag-theme-balham-dark { font-family: var(--trace-viewer-ui-font-family) !important; font-size: var(--trace-viewer-content-font-size) !important; } @@ -315,7 +337,7 @@ canvas { background-color: var(--trace-viewer-selection-background) !important; } -.ag-header-row:nth-child(2) > .ag-header-cell { +.ag-header-row:nth-child(2)>.ag-header-cell { padding-left: 0px !important; padding-right: 0px !important; } @@ -344,25 +366,29 @@ canvas { outline: none; user-select: none; } -.options-menu-drop-down > ul { + +.options-menu-drop-down>ul { list-style-type: none; margin: 0; padding: 0; display: table; width: 100%; } + .drop-down-list-item { min-height: var(--trace-viewer-private-menu-item-height); max-height: var(--trace-viewer-private-menu-item-height); padding: 0px; line-height: var(--trace-viewer-private-menu-item-height); } + .drop-down-list-item:hover { background: var(--trace-viewer-menu-selectionBackground); color: var(--trace-viewer-menu-selectionForeground); border: thin solid var(--trace-viewer-menu-selectionBorder); opacity: 1; } + .drop-down-list-item-text { padding-left: 18%; } @@ -405,7 +431,7 @@ canvas { color: var(--trace-viewer-descriptionForeground); border: thin solid var(--trace-viewer-disabledForeground); border-top: none; - } +} .pagination-button { border: none; @@ -454,9 +480,11 @@ canvas { padding-right: 0px; color: var(--trace-viewer-ui-font-color0); } + .remove-search-button:hover { cursor: pointer; } + .remove-search-button:focus { outline: none; box-shadow: none; diff --git a/packages/react-components/style/trace-explorer.css b/packages/react-components/style/trace-explorer.css index f6a5ca88..44f71a2d 100644 --- a/packages/react-components/style/trace-explorer.css +++ b/packages/react-components/style/trace-explorer.css @@ -249,6 +249,12 @@ text-decoration: underline; } +.source-code-tooltip:hover { + background-color: var(--trace-viewer-list-hoverBackground); + color: var(--trace-viewer-list-hoverForeground); + cursor: pointer; +} + .ag-react-container { overflow: hidden; text-overflow: ellipsis;