cabana/src/components/CanLog.js

265 lines
10 KiB
JavaScript

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactList from 'react-list';
import { StyleSheet, css } from 'aphrodite/no-important';
import cx from 'classnames';
import { formatMsgDec, formatMsgHex } from '../models/can-msg-fmt';
import { elementWiseEquals } from '../utils/array';
import Images from '../styles/images';
export default class CanLog extends Component {
static ITEMS_PER_PAGE = 50;
static propTypes = {
plottedSignals: PropTypes.array,
segmentIndices: PropTypes.array,
onSignalUnplotPressed: PropTypes.func,
onSignalPlotPressed: PropTypes.func,
message: PropTypes.object,
messageIndex: PropTypes.number,
onMessageExpanded: PropTypes.func,
};
constructor(props) {
super(props);
// only want to display up to length elements at a time
// offset, length
this.state = {
length: 0,
expandedMessages: [],
messageHeights: [],
allPacketsExpanded: false
}
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
this.renderLogListItem = this.renderLogListItem.bind(this);
this.renderLogList = this.renderLogList.bind(this);
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
}
componentWillReceiveProps(nextProps) {
if(nextProps.message && !this.props.message) {
this.addDisplayedMessages();
}
}
shouldComponentUpdate(nextProps, nextState) {
const curMessageLength = this.props.message ? this.props.message.entries.length : 0;
const nextMessageLength = nextProps.message ? nextProps.message.entries.length : 0;
const shouldUpdate = nextMessageLength != curMessageLength
|| nextProps.messageIndex != this.props.messageIndex
|| nextProps.plottedSignals.length != this.props.plottedSignals.length
|| JSON.stringify(nextProps.segmentIndices) != JSON.stringify(this.props.segmentIndices)
|| JSON.stringify(nextState) != JSON.stringify(this.state)
|| this.props.message != nextProps.message
|| (this.props.message !== undefined
&& nextProps.message !== undefined
&&
(
(this.props.message.signals
&& nextProps.message.signals
&& JSON.stringify(this.props.message.signals) != JSON.stringify(nextProps.message.signals))
||
(JSON.stringify(this.props.message.frame) != JSON.stringify(nextProps.message.frame))
));
return shouldUpdate;
}
addDisplayedMessages() {
const {length} = this.state;
const newLength = length + CanLog.ITEMS_PER_PAGE;
this.setState({length: newLength});
}
expandMessage(msg, msgIdx) {
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
this.props.onMessageExpanded();
}
collapseMessage(msg, msgIdx) {
this.setState({expandedMessages: this.state.expandedMessages
.filter((expMsgTime) => expMsgTime !== msg.time)})
}
isSignalPlotted(msgId, signalName) {
const plottedSignal = this.props.plottedSignals.find((plot) =>
plot.some((signal) => signal.messageId == msgId && signal.signalName == signalName));
return plottedSignal !== undefined;
}
signalValuePretty(signal, value) {
if(signal.isFloat) {
return value.toFixed(3);
} else return value;
}
isMessageExpanded(msg) {
return this.state.expandedMessages.indexOf(msg.time) !== -1;
}
toggleSignalPlot(msg, name, plotted) {
if (!plotted) {
this.props.onSignalPlotPressed(msg, name);
} else {
this.props.onSignalUnplotPressed(msg, name);
}
}
toggleExpandPacketSignals(msg) {
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msg);
const msgHasSignals = Object.keys(msg.signals).length > 0;
if (msgIsExpanded && msgHasSignals) {
this.setState({expandedMessages: this.state.expandedMessages
.filter((expMsgTime) => expMsgTime !== msg.time)})
} else if (msgHasSignals) {
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
this.props.onMessageExpanded();
} else { return; }
}
renderLogListItemSignals(msg) {
const { message } = this.props;
return (
<div className='signals-log-list-signals'>
{ Object.entries(msg.signals).map(([name, value]) => {
return [name, value, this.isSignalPlotted(message.id, name)]
}).map(([name, value, isPlotted]) => {
const signalValue = msg.signals[name];
const plottedButtonClass = isPlotted ? null : 'button--alpha';
const plottedButtonText = isPlotted ? 'Hide Plot' : 'Show Plot';
const signal = message.frame.signals[name];
const unit = signal.unit.length > 0 ? signal.unit : 'units';
return (
<div key={ name } className='signals-log-list-signal'>
<div className='signals-log-list-signal-message'>
<span>{ name }</span>
</div>
<div className='signals-log-list-signal-value'>
<span>
(<strong>{ this.signalValuePretty(signalValue, value) }</strong> { unit })
</span>
</div>
<div className='signals-log-list-signal-action'
onClick={ () => { this.toggleSignalPlot(this.props.message.id, name, isPlotted) } }>
<button className={ cx('button--tiny', plottedButtonClass) }>
<span>{ plottedButtonText }</span>
</button>
</div>
</div>
);
})}
</div>
)
}
renderLogListItemMessage(msg, key) {
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msg);
const msgHasSignals = Object.keys(msg.signals).length > 0;
const hasSignalsClass = msgHasSignals ? 'has-signals' : null;
const expandedClass = msgIsExpanded ? 'is-expanded' : null;
const row = (
<div key={key} className={cx('signals-log-list-item', hasSignalsClass, expandedClass)}>
<div className='signals-log-list-item-header'
onClick={ () => { this.toggleExpandPacketSignals(msg) } }>
<div className='signals-log-list-message'>
<strong>{(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id}</strong>
</div>
<div className='signals-log-list-time'>
<span>[{msg.relTime.toFixed(3)}]</span>
</div>
<div className='signals-log-list-bytes'>
<span className='t-mono'>{msg.hexData}</span>
</div>
</div>
<div className='signals-log-list-item-body'>
{ msgIsExpanded ? this.renderLogListItemSignals(msg) : null}
</div>
</div>
);
return row;
}
renderLogListItem(index, key) {
let offset = this.props.messageIndex;
if(offset === 0 && this.props.segmentIndices.length === 2) {
offset = this.props.segmentIndices[0];
}
return this.renderLogListItemMessage(this.props.message.entries[offset + index], key);
}
renderLogList(items, ref) {
return (
<div className='signals-log-list'>
<div className='signals-log-list-header'>
<div className='signals-log-list-message'>Message</div>
<div className='signals-log-list-time'>Time</div>
<div className='signals-log-list-bytes'>Bytes</div>
</div>
<div className='signals-log-list-items'
ref={ref}>
{items}
</div>
</div>
)
}
listLength() {
const {segmentIndices, messageIndex} = this.props;
if(messageIndex > 0) {
return this.props.message.entries.length - messageIndex;
} else if(segmentIndices.length == 2) {
return segmentIndices[1] - segmentIndices[0];
} else if(this.props.message) {
return this.props.message.entries.length;
} else {
// no message yet
return 0;
}
}
onExpandAllChanged(e) {
this.setState({allPacketsExpanded: e.target.checked});
}
toggleExpandAllPackets() {
this.setState({allPacketsExpanded: !this.state.allPacketsExpanded});
}
render() {
let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All';
let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha';
return (
<div className='cabana-explorer-signals-log'>
<div className='cabana-explorer-signals-log-header'>
<strong>Message Packets</strong>
<button className={cx('button--tiny', expandAllClass)}
onClick={this.toggleExpandAllPackets}>
{expandAllText}
</button>
</div>
<div className='cabana-explorer-signals-log-body'>
<ReactList
itemRenderer={this.renderLogListItem}
itemsRenderer={this.renderLogList}
length={this.listLength()}
pageSize={50}
updateWhenThisValueChanges={this.props.messageIndex}
type='variable' />
</div>
</div>
);
}
}