Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/base/src/signals/signal-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export declare interface SignalManager {
}

export const Signals = {
EXPERIMENT_CHANGED: 'tab changed',
TRACE_OPENED: 'trace opened',
TRACE_CLOSED: 'trace closed',
EXPERIMENT_OPENED: 'experiment opened',
Expand All @@ -43,6 +44,9 @@ export const Signals = {
};

export class SignalManager extends EventEmitter implements SignalManager {
fireExperimentChangedSignal(tabName: string, experimentUUID: string): void {
this.emit(Signals.EXPERIMENT_CHANGED, { tabName, experimentUUID });
}
fireTraceOpenedSignal(trace: Trace): void {
this.emit(Signals.TRACE_OPENED, trace);
}
Expand Down
148 changes: 148 additions & 0 deletions packages/react-components/src/trace-explorer/menu-item-trace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import * as React from 'react';
import { Trace } from 'tsp-typescript-client';
import { signalManager, Signals } from '@trace-viewer/base/lib/signals/signal-manager';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the package was renamed: use traceviewer-base instead of @trace-viewer/base

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';

interface MenuItemProps {
index: number;
experimentName: string;
experimentUUID: string;
traces: Trace[];
onExperimentNameChange: (newExperimentName: string, index: number) => void;
menuItemTraceContainerClassName: string;
handleClickEvent: (event: React.MouseEvent<HTMLDivElement>, experimentName: string) => void;
handleContextMenuEvent: (event: React.MouseEvent<HTMLDivElement>, experimentUUID: string) => void;
}
interface MenuItemState {
editingTab: boolean;
oldExperimentName: string;
experimentNameState: string;
}

export class MenuItemTrace extends React.Component<MenuItemProps, MenuItemState> {

private wrapper: React.RefObject<HTMLDivElement>;

constructor(menuItemProps: MenuItemProps) {
super(menuItemProps);
this.state = {
oldExperimentName: this.props.experimentName,
experimentNameState: this.props.experimentName,
editingTab: false
};
this.wrapper = React.createRef();
this.handleClickOutside = this.handleClickOutside.bind(this);
}

submitNewExperimentName(): void {
if (this.state.experimentNameState.length > 0) {
this.setState({
editingTab: false,
oldExperimentName: this.state.experimentNameState
});
this.props.onExperimentNameChange(this.state.experimentNameState, this.props.index);
} else {
this.setState({
editingTab: false,
experimentNameState: this.state.oldExperimentName
});
const tabName = 'Trace: ' + this.state.oldExperimentName;
signalManager().fireExperimentChangedSignal(tabName, this.props.experimentUUID);
}
document.removeEventListener('click', this.handleClickOutside);
}

handleClickOutside = (event: Event): void => {
const node = this.wrapper.current;
if ((!node || !node.contains(event.target as Node)) && this.state.editingTab === true) {
this.submitNewExperimentName();
}
};

protected handleEnterPress(event: React.KeyboardEvent<HTMLInputElement>): void{
if (event.key === 'Enter' && this.state.editingTab === true){
this.submitNewExperimentName();
}
}

protected changeText(event: React.ChangeEvent<HTMLInputElement>): void{
let newName = event.target.value.toString();
this.setState({
experimentNameState : newName
});
newName = 'Trace: ' + newName;
signalManager().fireExperimentChangedSignal(newName, this.props.experimentUUID);
}
protected inputTab(): React.ReactNode {
if (!this.state.editingTab) {
return (
<div className='wrapper'>
{this.state.experimentNameState}
<div className='edit-trace-name' onClick={e => {this.renderEditTraceName(e);}}>
<FontAwesomeIcon icon={faPencilAlt} />
</div>
</div>
);
}
return (<input name="tab-name" className="theia-input name-input-box"
defaultValue = {this.state.experimentNameState}
onChange = {e => (this.changeText(e))}
onClick = {e => e.stopPropagation()}
onKeyPress = {e => (this.handleEnterPress(e))}
maxLength = {50}
/>);
}
protected renderEditTraceName(event: React.MouseEvent<HTMLDivElement>): void {
document.addEventListener('click', this.handleClickOutside);
this.setState(() => ({
editingTab: true
}));
event.stopPropagation();
event.preventDefault();
}
protected renderTracesForExperiment = (): React.ReactNode => this.doRenderTracesForExperiment();
protected doRenderTracesForExperiment(): React.ReactNode {
const tracePaths = this.props.traces;
return (
<div className='trace-element-path-container'>
{tracePaths.map(trace => (
<div className='trace-element-path child-element' id={trace.UUID} key={trace.UUID}>
{` > ${trace.name}`}
</div>
))}
</div>
);
}
protected subscribeToExplorerEvents(): void {
signalManager().on(Signals.OUTPUT_ADDED, this.changeText);
}

render(): JSX.Element {
return (
<div className={this.props.menuItemTraceContainerClassName}
id={`${this.props.menuItemTraceContainerClassName}-${this.props.index}`}
onClick={event => {
this.props.handleClickEvent(event, this.props.experimentUUID);
}
}
onContextMenu={event => { this.props.handleContextMenuEvent(event, this.props.experimentUUID); }}
data-id={`${this.props.index}`}
ref={this.wrapper}>
<div className='trace-element-container'>
<div className='trace-element-info' >
<h4 className='trace-element-name'>
{this.inputTab()}
</h4>
{ this.renderTracesForExperiment() }
</div>
{/* <div className='trace-element-options'>
<button className='share-context-button' onClick={this.handleShareButtonClick.bind(this, props.index)}>
<FontAwesomeIcon icon={faShareSquare} />
</button>
</div> */}
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { OpenedTracesUpdatedSignalPayload } from '@trace-viewer/base/lib/signals/opened-traces-updated-signal-payload';
import { ITspClientProvider } from '@trace-viewer/base/lib/tsp-client-provider';
import { MenuItemTrace } from './menu-item-trace';

export interface ReactOpenTracesWidgetProps {
id: string,
Expand Down Expand Up @@ -44,6 +45,7 @@ export class ReactOpenTracesWidget extends React.Component<ReactOpenTracesWidget
signalManager().on(Signals.EXPERIMENT_CLOSED, this._onExperimentClosed);
signalManager().on(Signals.TRACEVIEWERTAB_ACTIVATED, this._onOpenedTracesWidgetActivated);

this.handleExperimentNameUpdate = this.handleExperimentNameUpdate.bind(this);
this._experimentManager = this.props.tspClientProvider.getExperimentManager();
this.props.tspClientProvider.addTspClientChangeListener(() => {
this._experimentManager = this.props.tspClientProvider.getExperimentManager();
Expand Down Expand Up @@ -109,7 +111,6 @@ export class ReactOpenTracesWidget extends React.Component<ReactOpenTracesWidget
render(): React.ReactNode {
const totalHeight = this.getTotalHeight();
this._forceUpdateKey = !this._forceUpdateKey;
const key = Number(this._forceUpdateKey);
return (
<>
<ReactModal isOpen={this._showShareDialog} onRequestClose={this.handleShareModalClose}
Expand All @@ -122,7 +123,6 @@ export class ReactOpenTracesWidget extends React.Component<ReactOpenTracesWidget
<AutoSizer>
{({ width }) =>
<List
key={key}
height={totalHeight}
width={width}
rowCount={this.state.openedExperiments.length}
Expand Down Expand Up @@ -151,25 +151,17 @@ export class ReactOpenTracesWidget extends React.Component<ReactOpenTracesWidget
if (props.index === this.state.selectedExperimentIndex) {
traceContainerClassName = traceContainerClassName + ' theia-mod-selected';
}
return <div className={traceContainerClassName}
id={`${traceContainerClassName}-${props.index}`}
key={props.key}
style={props.style}
onClick={event => { this.handleClickEvent(event, traceUUID); }}
onContextMenu={event => { this.handleContextMenuEvent(event, traceUUID); }}
data-id={`${props.index}`}>
<div className='trace-element-container'>
<div className='trace-element-info' >
<h4 className='trace-element-name'>{traceName}</h4>
{this.renderTracesForExperiment(props.index)}
</div>
{/* <div className='trace-element-options'>
<button className='share-context-button' onClick={this.handleShareButtonClick.bind(this, props.index)}>
<FontAwesomeIcon icon={faShareSquare} />
</button>
</div> */}
</div>
</div>;
return <MenuItemTrace
onExperimentNameChange = {this.handleExperimentNameUpdate}
menuItemTraceContainerClassName = {traceContainerClassName}
traces={this.state.openedExperiments[props.index].traces}
experimentName={traceName}
experimentUUID={traceUUID}
key={traceUUID}
index={props.index}
handleContextMenuEvent={this.handleContextMenuEvent}
handleClickEvent={this.handleClickEvent}
/>;
}

protected renderTracesForExperiment(index: number): React.ReactNode {
Expand Down Expand Up @@ -235,15 +227,33 @@ export class ReactOpenTracesWidget extends React.Component<ReactOpenTracesWidget

protected async doUpdateOpenedExperiments(): Promise<void> {
const remoteExperiments = await this._experimentManager.getOpenedExperiments();

remoteExperiments.forEach(experiment => {
this._experimentManager.addExperiment(experiment);
});

remoteExperiments.forEach(newExp => {
this.state.openedExperiments.forEach(oldExp =>{
if (newExp.UUID === oldExp.UUID) {
newExp.name = oldExp.name;
}
});
});

const selectedIndex = remoteExperiments.findIndex(experiment => this._selectedExperiment &&
experiment.UUID === this._selectedExperiment.UUID);
this.setState({ openedExperiments: remoteExperiments, selectedExperimentIndex: selectedIndex !== -1 ? selectedIndex : 0 });
signalManager().fireOpenedTracesChangedSignal(new OpenedTracesUpdatedSignalPayload(remoteExperiments ? remoteExperiments.length : 0));
}

protected handleExperimentNameUpdate(newExperimentName: string, index: number): void {
const modifiedOpenedExperiments = this.state.openedExperiments.slice();
modifiedOpenedExperiments[index].name = newExperimentName;
this.setState({
openedExperiments: modifiedOpenedExperiments
});
}

protected handleShareButtonClick = (index: number): void => this.doHandleShareButtonClick(index);

protected doHandleShareButtonClick(index: number): void {
Expand Down
29 changes: 27 additions & 2 deletions packages/react-components/style/trace-explorer.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,29 @@

.trace-list-container.theia-mod-selected,
.outputs-list-container.theia-mod-selected {
background-color: var(--theia-selection-background);
background-color: #3b3a4a;
}

.trace-list-container:hover .edit-trace-name {
visibility: visible;
float: right;
}

.trace-list-container:hover .wrapper {
background-color: #151515;
}

.trace-list-container .edit-trace-name {
visibility: hidden;
}

.name-input-box {
background-color: #151515;
margin-bottom: 5px;
width: 100%;
height: 18px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}

/* Share options have been commented out, grid is disabled to optimize horizontal space */
Expand All @@ -75,7 +97,10 @@

.trace-element-name, .outputs-element-name {
font-weight: bold;
margin: unset;
margin-top: unset;
margin-left: unset;
margin-right: unset;
margin-bottom: 2px;
height: var(--trace-extension-list-line-height);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class TraceViewerWidget extends ReactWidget {
private onOutputAdded = (payload: OutputAddedSignalPayload): void => this.doHandleOutputAddedSignal(payload);
private onExperimentSelected = (experiment: Experiment): void => this.doHandleExperimentSelectedSignal(experiment);
private onCloseExperiment = (UUID: string): void => this.doHandleCloseExperimentSignal(UUID);
private onExperimentNameChange = (payload: { tabName: string, experimentUUID: string; }): void => this.doHandleExperimentNameChange(payload);

@inject(TraceViewerWidgetOptions) protected readonly options: TraceViewerWidgetOptions;
@inject(TspClientProvider) protected tspClientProvider: TspClientProvider;
Expand Down Expand Up @@ -111,13 +112,20 @@ export class TraceViewerWidget extends ReactWidget {
signalManager().on(Signals.OUTPUT_ADDED, this.onOutputAdded);
signalManager().on(Signals.EXPERIMENT_SELECTED, this.onExperimentSelected);
signalManager().on(Signals.CLOSE_TRACEVIEWERTAB, this.onCloseExperiment);
signalManager().on(Signals.EXPERIMENT_CHANGED, this.onExperimentNameChange);
}

protected updateBackgroundTheme(): void {
const currentThemeType = ThemeService.get().getCurrentTheme().type;
signalManager().fireThemeChangedSignal(currentThemeType);
}

protected doHandleExperimentNameChange(payload: { tabName: string, experimentUUID: string; }): void {
if (payload.experimentUUID === this.options.traceUUID) {
this.title.label = payload.tabName;
}
}

dispose(): void {
super.dispose();
signalManager().off(Signals.OUTPUT_ADDED, this.onOutputAdded);
Expand Down