diff --git a/local-libs/traceviewer-libs/react-components/package.json b/local-libs/traceviewer-libs/react-components/package.json index cae2c4b9..147a3757 100644 --- a/local-libs/traceviewer-libs/react-components/package.json +++ b/local-libs/traceviewer-libs/react-components/package.json @@ -28,6 +28,7 @@ "@vscode/codicons": "^0.0.29", "chart.js": "^2.8.0", "d3": "^7.1.1", + "json-edit-react": "1.28.2", "lodash": "^4.17.15", "react-chartjs-2": "^2.7.6", "react-contexify": "^5.0.0", diff --git a/local-libs/traceviewer-libs/react-components/src/components/data-output-component.tsx b/local-libs/traceviewer-libs/react-components/src/components/data-output-component.tsx new file mode 100644 index 00000000..3dee0068 --- /dev/null +++ b/local-libs/traceviewer-libs/react-components/src/components/data-output-component.tsx @@ -0,0 +1,180 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AbstractOutputComponent, AbstractOutputProps, AbstractOutputState } from './abstract-output-component'; +import * as React from 'react'; +import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper'; +import { ResponseStatus } from 'tsp-typescript-client/lib/models/response/responses'; +import { ObjectModel } from 'tsp-typescript-client/lib/models/object'; +import { isEmpty } from 'lodash'; +import { JSONBigUtils } from 'tsp-typescript-client/lib/utils/jsonbig-utils'; +import { JsonEditor } from 'json-edit-react'; +import debounce from 'lodash.debounce'; +import '../../style/react-contextify.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSpinner } from '@fortawesome/free-solid-svg-icons'; + +type DataOutputProps = AbstractOutputProps & {}; + +type DataOuputState = AbstractOutputState & { + model: ObjectModel +}; + +const MENU_ID = 'Data.context.menuId '; + +export class DataOutputComponent extends AbstractOutputComponent { + dataRef: React.RefObject = React.createRef(); + + private _debouncedFetchData = debounce(() => this.fetchData(), 500); + + constructor(props: AbstractOutputProps) { + super(props); + this.state = { + outputStatus: ResponseStatus.RUNNING, + model: { object: {} }, + }; + this.addPinViewOptions(); + } + + componentDidMount(): void { + this.waitAnalysisCompletion(); + } + + async fetchData(navObject?: { [key: string]: any }, scroll?: () => void): Promise { + const useSelectionRange = this.props.outputDescriptor.capabilities?.selectionRange; + const parameters = useSelectionRange && this.props.selectionRange + ? QueryHelper.query({ ...navObject, 'count' : 500, 'selection_range' : { 'start': this.props.selectionRange.getStart(), 'end': this.props.selectionRange.getEnd() } } ) + : QueryHelper.query({ ...navObject, 'count' : 500 }); + const tspClientResponse = await this.props.tspClient.fetchObject( + this.props.traceId, + this.props.outputDescriptor.id, + parameters + ); + const modelResponse = tspClientResponse.getModel(); + if (tspClientResponse.isOk() && modelResponse) { + if (modelResponse.model) { + this.setState({ + outputStatus: modelResponse.status, + model: modelResponse.model + }, scroll); + } else { + this.setState({ + outputStatus: modelResponse.status + }); + } + return modelResponse.status; + } + this.setState({ + outputStatus: ResponseStatus.FAILED + }); + return ResponseStatus.FAILED; + } + + resultsAreEmpty(): boolean { + return isEmpty(this.state.model); + } + + renderMainArea(): React.ReactNode { + return ( + + {this.state.outputStatus === ResponseStatus.COMPLETED ? ( +
+
+ {this.renderPrevButton()} + {this.renderObject()} + {this.renderNextButton()} +
+
+ ) : ( +
+ + Analysis running +
+ )} +
+ ); + } + + private renderObject() { + const replacer = (_key: any, value: any) => { + return (typeof value === 'bigint') ? value.toString() + 'n' : value; + }; + const obj = JSON.parse(JSONBigUtils.stringify(this.state.model.object, null, '\t')); + return ( + + ); + } + private renderPrevButton() { + const navObject = { previous: this.state.model.previous }; + const scroll = () => this.dataRef.current?.scrollTo({ top: this.dataRef.current.scrollHeight, left: 0 }); + return ( + + {navObject.previous != undefined ? ( +


+ ) : ( + <> + )} +
+ ); + } + + private renderNextButton() { + const navObject = { next: this.state.model.next }; + const scroll = () => this.dataRef.current?.scrollTo({ top: 0, left: 0 }); + return ( + + {navObject.next != undefined ? ( +


+ ) : ( + <> + )} +
+ ); + } + + setFocus(): void { + if (document.getElementById(this.props.traceId + this.props.outputDescriptor.id + 'focusContainer')) { + document.getElementById(this.props.traceId + this.props.outputDescriptor.id + 'focusContainer')?.focus(); + } else { + document.getElementById(this.props.traceId + this.props.outputDescriptor.id)?.focus(); + } + } + + protected async waitAnalysisCompletion(): Promise { + let outputStatus = this.state.outputStatus; + const timeout = 500; + while (this.state && outputStatus === ResponseStatus.RUNNING) { + outputStatus = await this.fetchData(); + await new Promise(resolve => setTimeout(resolve, timeout)); + } + } + + componentWillUnmount(): void { + // fix Warning: Can't perform a React state update on an unmounted component + this.setState = (_state, _callback) => undefined; + } + + async componentDidUpdate(prevProps: DataOutputProps): Promise { + if (this.props.selectionRange && this.props.selectionRange !== prevProps.selectionRange) { + this._debouncedFetchData(); + } + } +} diff --git a/local-libs/traceviewer-libs/react-components/src/components/trace-context-component.tsx b/local-libs/traceviewer-libs/react-components/src/components/trace-context-component.tsx index 13e1fd5e..e4b91335 100644 --- a/local-libs/traceviewer-libs/react-components/src/components/trace-context-component.tsx +++ b/local-libs/traceviewer-libs/react-components/src/components/trace-context-component.tsx @@ -22,6 +22,7 @@ import { AbstractOutputProps } from './abstract-output-component'; import * as Messages from 'traceviewer-base/lib/message-manager'; import { signalManager } from 'traceviewer-base/lib/signals/signal-manager'; import { BIMath } from 'timeline-chart/lib/bigint-utils'; +import { DataOutputComponent } from './data-output-component'; import { DataTreeOutputComponent } from './datatree-output-component'; import { cloneDeep } from 'lodash'; import { UnitControllerHistoryHandler } from './utils/unit-controller-history-handler'; @@ -867,6 +868,14 @@ export class TraceContextComponent extends React.Component ); } + case ProviderType.DATA: + return ( + + ); default: return (