forked from eclipse-cdt-cloud/theia-trace-extension
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds the Time Range Data Widget to the sidebar as specified in ADR eclipse-cdt-cloud#8: Time Range Data Widget The unit controller is linked to the widget by signals. When a new active tab is loaded or changed, the trace-viewer Theia component dispatches the new active unit controller via signal-manager. This signal is picked up by the time-range-data-widget react component. Because of this, the unit controller is now a public value in the trace-context-component. Signed-off-by: William Yang <william.yang@ericsson.com>
- Loading branch information
1 parent
84f8d2f
commit 0310a56
Showing
7 changed files
with
338 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
262 changes: 262 additions & 0 deletions
262
packages/react-components/src/trace-explorer/trace-explorer-time-range-data-widget.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
import * as React from 'react'; | ||
import { TimelineChart } from 'timeline-chart/lib/time-graph-model'; | ||
import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller'; | ||
import { signalManager, Signals } from 'traceviewer-base/lib/signals/signal-manager'; | ||
|
||
export interface ReactTimeRangeDataWidgetProps { | ||
id: string, | ||
title: string, | ||
} | ||
|
||
export interface ReactTimeRangeDataWidgetState { | ||
unitController?: TimeGraphUnitController, | ||
viewRange?: TimelineChart.TimeGraphRange, | ||
selectionRange?: TimelineChart.TimeGraphRange, | ||
offset?: bigint, | ||
userInputSelectionStartIsValid: boolean, | ||
userInputSelectionEndIsValid: boolean, | ||
userInputSelectionStart?: bigint, | ||
userInputSelectionEnd?: bigint, | ||
inputting: boolean, | ||
} | ||
|
||
export class ReactTimeRangeDataWidget extends React.Component<ReactTimeRangeDataWidgetProps, ReactTimeRangeDataWidgetState> { | ||
|
||
private selectionStartInput: React.RefObject<HTMLInputElement>; | ||
private selectionEndInput: React.RefObject<HTMLInputElement>; | ||
|
||
constructor(props: ReactTimeRangeDataWidgetProps) { | ||
super(props); | ||
this.selectionEndInput = React.createRef(); | ||
this.selectionStartInput = React.createRef(); | ||
this.state = { | ||
inputting: false, | ||
userInputSelectionStartIsValid: true, | ||
userInputSelectionEndIsValid: true, | ||
}; | ||
signalManager().on(Signals.NEW_ACTIVE_UNIT_CONTROLLER, this.onNewActiveUnitController); | ||
} | ||
|
||
componentWillUnmount = (): void => { | ||
this.removeHandlers(); | ||
signalManager().off(Signals.NEW_ACTIVE_UNIT_CONTROLLER, this.onNewActiveUnitController); | ||
}; | ||
|
||
addHandlers = (): void => { | ||
this.state.unitController?.onSelectionRangeChange(this.onUnitControllerValueChange); | ||
this.state.unitController?.onViewRangeChanged(this.onUnitControllerValueChange); | ||
}; | ||
|
||
removeHandlers = (): void => { | ||
this.state.unitController?.removeSelectionRangeChangedHandler(this.onUnitControllerValueChange); | ||
this.state.unitController?.removeViewRangeChangedHandler(this.onUnitControllerValueChange); | ||
}; | ||
|
||
onNewActiveUnitController = (unitController: TimeGraphUnitController): void => { | ||
this.removeHandlers(); | ||
const { viewRange, selectionRange, offset } = unitController; | ||
this.setState({ unitController, viewRange, selectionRange, offset }, this.addHandlers); | ||
}; | ||
|
||
onUnitControllerValueChange = (): void => { | ||
if (!this.state.unitController) { | ||
return; | ||
} | ||
const { viewRange, selectionRange, offset } = this.state.unitController; | ||
this.setState({ viewRange, selectionRange, offset, inputting: false }, this.setFormInputValuesToUnitControllersValue); | ||
}; | ||
|
||
setFormInputValuesToUnitControllersValue = (): void => { | ||
const { selectionRange } = this.state; | ||
const { start , end } = this.getStartAndEnd(selectionRange?.start, selectionRange?.end); | ||
if (this.selectionStartInput.current && this.selectionEndInput.current) { | ||
this.selectionStartInput.current.value = start; | ||
this.selectionEndInput.current.value = end; | ||
} | ||
this.setState({ | ||
userInputSelectionEndIsValid: true, | ||
userInputSelectionStartIsValid: true, | ||
userInputSelectionEnd: undefined, | ||
userInputSelectionStart: undefined, | ||
inputting: false, | ||
}); | ||
}; | ||
|
||
onChange = (event: React.FormEvent<HTMLInputElement>, inputIndex: number): void => { | ||
event.preventDefault(); | ||
if (!this.state.inputting) { | ||
this.setState({ inputting: true }); | ||
} | ||
|
||
// BigInt("") => 0 but we want that to be undefined. | ||
const value = event.currentTarget.value === '' ? undefined : BigInt(event.currentTarget.value); | ||
|
||
switch (inputIndex) { | ||
case 0: | ||
this.setState({ userInputSelectionStart: value }); | ||
return; | ||
case 1: | ||
this.setState({ userInputSelectionEnd: value }); | ||
return; | ||
default: | ||
throw Error('Input index is invalid!'); | ||
} | ||
|
||
}; | ||
|
||
onSubmit = (event: React.FormEvent): void => { | ||
this.verifyUserInput(); | ||
event.preventDefault(); | ||
}; | ||
|
||
onCancel = (): void => { | ||
this.setFormInputValuesToUnitControllersValue(); | ||
}; | ||
|
||
/** | ||
* | ||
* Sometimes the unitController's selection range has a start that's larger than the end (they're reversed). | ||
* This always sets the lesser number as the start. | ||
* @param value1 | ||
* @param value2 | ||
* @returns { start: string, end: string } | ||
*/ | ||
getStartAndEnd = (v1: bigint | string | undefined, v2: bigint | string | undefined): { start: string, end: string } => { | ||
const { unitController, offset } = this.state; | ||
if (!unitController || !offset || v1 === undefined || v2 === undefined) { | ||
return { start: '', end: '' }; | ||
} | ||
|
||
v1 = BigInt(v1); | ||
v2 = BigInt(v2); | ||
|
||
const reverse = v1 > v2; | ||
const start = reverse ? v2 : v1; | ||
const end = reverse ? v1 : v2; | ||
|
||
// We display values in absolute time with the offset. | ||
return { | ||
start: (start + offset).toString(), | ||
end: (end + offset).toString() | ||
}; | ||
}; | ||
|
||
verifyUserInput = (): void => { | ||
let { unitController, userInputSelectionStart, userInputSelectionEnd } = this.state; | ||
|
||
// We need at least one value to change: start or end. | ||
if (!unitController || (!userInputSelectionStart && !userInputSelectionEnd)) { | ||
this.setFormInputValuesToUnitControllersValue(); | ||
return; | ||
} | ||
const { offset, absoluteRange, selectionRange } = unitController; | ||
|
||
// If there is no pre-existing selection range and the user only inputs one value | ||
// Make that both selection range start and end value | ||
if (!selectionRange && (!userInputSelectionEnd || !userInputSelectionStart)) { | ||
userInputSelectionStart = userInputSelectionStart || userInputSelectionEnd; | ||
userInputSelectionEnd = userInputSelectionEnd || userInputSelectionStart; | ||
} | ||
|
||
// If there is no user input for start or end, set that value to the current unit controller value. | ||
userInputSelectionStart = typeof userInputSelectionStart === 'bigint' ? userInputSelectionStart : | ||
// Below is added to satisfy typescript compiler | ||
!selectionRange ? offset : | ||
// We also need to account for backwards selection start / end here. | ||
selectionRange.start <= selectionRange.end ? selectionRange.start + offset : | ||
selectionRange.end + offset; | ||
userInputSelectionEnd = typeof userInputSelectionEnd === 'bigint' ? userInputSelectionEnd : | ||
!selectionRange ? offset : | ||
selectionRange.start <= selectionRange.end ? selectionRange.end + offset : | ||
selectionRange.start + offset; | ||
|
||
const isValid = (n: bigint): boolean => (n >= offset) && (n <= absoluteRange + offset); | ||
const startValid = isValid(userInputSelectionStart); | ||
const endValid = isValid(userInputSelectionEnd); | ||
|
||
if (startValid && endValid) { | ||
unitController.selectionRange = { | ||
start: userInputSelectionStart - offset, | ||
end: userInputSelectionEnd - offset | ||
}; | ||
} else { | ||
this.setState({ | ||
userInputSelectionStartIsValid: startValid, | ||
userInputSelectionEndIsValid: endValid, | ||
}); | ||
} | ||
}; | ||
|
||
render(): React.ReactNode { | ||
|
||
const { | ||
viewRange, | ||
selectionRange, | ||
inputting, | ||
userInputSelectionStartIsValid, | ||
userInputSelectionEndIsValid, | ||
} = this.state; | ||
|
||
const sectionClassName = 'view-range-widget-section'; | ||
const errorClassName = `${sectionClassName} invalid-input`; | ||
|
||
const { start: viewRangeStart, end: viewRangeEnd } = this.getStartAndEnd(viewRange?.start, viewRange?.end); | ||
const { start: selectionRangeStart, end: selectionRangeEnd } = this.getStartAndEnd(selectionRange?.start, selectionRange?.end); | ||
|
||
const startValid = inputting ? userInputSelectionStartIsValid : true; | ||
const endValid = inputting ? userInputSelectionEndIsValid : true; | ||
|
||
return ( | ||
<div className='trace-explorer-item-properties'> | ||
<div className='trace-explorer-panel-content'> | ||
<form onSubmit={this.onSubmit}> | ||
{(!startValid || !endValid) && ( | ||
<div className={errorClassName}> | ||
<label htmlFor="errorMessage"> | ||
<h4 className='outputs-element-name'><i>Invalid values</i></h4> | ||
</label> | ||
</div> | ||
)} | ||
<div className={sectionClassName}> | ||
<label htmlFor="viewRangeStart"> | ||
<h4 className='outputs-element-name'>View Range Start:</h4> | ||
{viewRangeStart} | ||
</label> | ||
</div> | ||
<div className={sectionClassName}> | ||
<label htmlFor="viewRangeEnd"> | ||
<h4 className='outputs-element-name'>View Range End:</h4> | ||
{viewRangeEnd} | ||
</label> | ||
</div> | ||
<div className={startValid ? sectionClassName : errorClassName}> | ||
<label htmlFor="selectionRangeStart"> | ||
<h4 className='outputs-element-name'>{userInputSelectionStartIsValid ? 'Selection Range Start:' : '* Selection Range Start:'}</h4> | ||
</label> | ||
<input | ||
ref={this.selectionStartInput} | ||
type="number" | ||
defaultValue={selectionRangeStart} | ||
onChange={e => this.onChange(e, 0)} | ||
/> | ||
</div> | ||
<div className={endValid ? sectionClassName : errorClassName}> | ||
<label htmlFor="selectionRangeEnd"> | ||
<h4 className='outputs-element-name'>{endValid ? 'Selection Range End:' : '* Selection Range End:'}</h4> | ||
</label> | ||
<input | ||
ref={this.selectionEndInput} | ||
type="number" | ||
defaultValue={selectionRangeEnd} | ||
onChange={e => this.onChange(e, 1)} | ||
/> | ||
</div> | ||
{inputting && (<div className={sectionClassName}> | ||
<input type="submit" value="Submit"/><input type="button" onClick={this.onCancel} value="Cancel"/> | ||
</div>)} | ||
</form> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
...trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-time-range-data-widget.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { inject, injectable, postConstruct } from 'inversify'; | ||
import { ReactWidget, Widget, Message, WidgetManager } from '@theia/core/lib/browser'; | ||
import * as React from 'react'; | ||
import { ReactTimeRangeDataWidget } from 'traceviewer-react-components/lib/trace-explorer//trace-explorer-time-range-data-widget'; | ||
|
||
@injectable() | ||
export class TraceExplorerTimeRangeDataWidget extends ReactWidget { | ||
static ID = 'trace-explorer-time-range-data'; | ||
static LABEL = 'Time Range Data'; | ||
|
||
@inject(WidgetManager) protected readonly widgetManager!: WidgetManager; | ||
|
||
@postConstruct() | ||
init(): void { | ||
this.id = TraceExplorerTimeRangeDataWidget.ID; | ||
this.title.label = TraceExplorerTimeRangeDataWidget.LABEL; | ||
this.update(); | ||
} | ||
|
||
render(): React.ReactNode { | ||
return <div> | ||
<ReactTimeRangeDataWidget | ||
id={this.id} | ||
title={this.title.label} | ||
/> | ||
</div>; | ||
} | ||
|
||
protected onResize(msg: Widget.ResizeMessage): void { | ||
super.onResize(msg); | ||
this.update(); | ||
} | ||
|
||
protected onAfterShow(msg: Message): void { | ||
super.onAfterShow(msg); | ||
this.update(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.