cabana/src/components/meta.js

297 lines
11 KiB
JavaScript

import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import cx from 'classnames';
import PropTypes from 'prop-types';
import Clipboard from 'clipboard';
require('core-js/fn/array/includes');
import {modifyQueryParameters} from '../utils/url';
import LoadDbcModal from './LoadDbcModal';
import * as GithubAuth from '../api/github-auth';
import Images from '../styles/images';
import MessageBytes from './MessageBytes';
import {GITHUB_AUTH_TOKEN_KEY} from '../config';
export default class Meta extends Component {
static propTypes = {
onMessageSelected: PropTypes.func,
onMessageUnselected: PropTypes.func,
dongleId: PropTypes.string,
name: PropTypes.string,
messages: PropTypes.objectOf(PropTypes.object),
selectedMessages: PropTypes.array,
onPartChanged: PropTypes.func,
partsCount: PropTypes.number,
showLoadDbc: PropTypes.func,
showSaveDbc: PropTypes.func,
dbcFilename: PropTypes.string,
dbcLastSaved: PropTypes.object, // moment.js object,
showEditMessageModal: PropTypes.func,
route: PropTypes.object,
partsLoaded: PropTypes.number,
currentParts: PropTypes.array,
seekTime: PropTypes.number,
loginWithGithub: PropTypes.element,
isDemo: PropTypes.bool,
};
constructor(props) {
super(props);
const {dbcLastSaved} = props;
this.state = {
filterText: 'Filter',
lastSaved: dbcLastSaved !== null ? this.props.dbcLastSaved.fromNow() : null,
hoveredMessages: []
};
this.onFilterChanged = this.onFilterChanged.bind(this);
this.onFilterFocus = this.onFilterFocus.bind(this);
this.onFilterUnfocus = this.onFilterUnfocus.bind(this);
this.msgKeyFilter = this.msgKeyFilter.bind(this);
}
componentWillMount() {
this.lastSavedTimer = setInterval(() => {
if(this.props.dbcLastSaved !== null) {
this.setState({lastSaved: this.props.dbcLastSaved.fromNow()})
}
}, 30000);
}
componentWillUnmount() {
window.clearInterval(this.lastSavedTimer);
}
componentWillReceiveProps(nextProps) {
if(nextProps.lastSaved !== this.props.lastSaved && typeof nextProps === 'object') {
this.setState({lastSaved: nextProps.dbcLastSaved.fromNow()})
}
const nextMsgKeys = Object.keys(nextProps.messages);
if(JSON.stringify(nextMsgKeys) != JSON.stringify(Object.keys(this.props.messages))) {
let {selectedMessages} = this.props;
selectedMessages = selectedMessages.filter((m) => nextMsgKeys.indexOf(m) !== -1);
this.setState({hoveredMessages: []});
}
}
onFilterChanged(e) {
let val = e.target.value;
if(val.trim() === 'Filter') val = '';
this.setState({filterText: val})
}
onFilterFocus(e) {
if(this.state.filterText.trim() == 'Filter') {
this.setState({filterText: ''})
}
}
onFilterUnfocus(e) {
if(this.state.filterText.trim() == '') {
this.setState({filterText: 'Filter'})
}
}
msgKeyFilter(key) {
const {filterText} = this.state;
const msg = this.props.messages[key];
const msgName = (msg.frame ? msg.frame.name : '');
return (filterText == 'Filter'
|| filterText == ''
|| key.toLowerCase().indexOf(filterText.toLowerCase()) !== -1
|| msgName.toLowerCase().indexOf(filterText.toLowerCase()) !== -1);
}
lastSavedPretty() {
const {dbcLastSaved} = this.props;
return dbcLastSaved.fromNow();
}
onMessageHover(key) {
let {hoveredMessages} = this.state;
if(hoveredMessages.indexOf(key) !== -1) return;
hoveredMessages.push(key);
this.setState({hoveredMessages});
}
onMessageHoverEnd(key) {
let {hoveredMessages} = this.state;
hoveredMessages = hoveredMessages.filter((m) => m != key);
this.setState({hoveredMessages});
}
onMsgRemoveClick(key) {
let {selectedMessages} = this.state;
selectedMessages = selectedMessages.filter((m) => m != key);
this.props.onMessageUnselected(key);
this.setState({selectedMessages});
}
onMessageSelected(key) {
// uncomment when we support multiple messages
// const selectedMessages = this.state.selectedMessages.filter((m) => m != key);
const selectedMessages = [];
selectedMessages.push(key);
this.props.updateSelectedMessages(selectedMessages);
this.props.onMessageSelected(key);
}
orderedMessages() {
const {messages} = this.props;
const keys = Object.keys(messages)
.filter(this.msgKeyFilter)
.sort((key1, key2) => {
const msg1 = messages[key1], msg2 = messages[key2];
if(msg1.entries.length < msg2.entries.length) {
return 1;
} else if(msg1.entries.length === msg2.entries.length) {
return 0;
} else {
return -1;
}
});
let bins = [];
keys.forEach((key, idx) => {
const msg = messages[key];
let bin = bins.find((bin) =>
bin.some((binMsg) =>
Math.abs(binMsg.entries.length - msg.entries.length) < 100
)
);
if(bin) {
bin.push(msg);
} else {
bins.push([msg]);
}
});
bins = bins.map((bin) => bin.sort((msg1, msg2) => {
if(msg1.address < msg2.address) {
return -1;
} else {
return 1;
}
})
);
return bins.reduce((arr, bin) => arr.concat(bin), []);
}
selectedMessageClass(messageId) {
return (this.props.selectedMessages.includes(messageId) ? 'is-selected' : null);
}
renderAvailableMessagesList() {
if(Object.keys(this.props.messages).length === 0) {
return <p>Loading messages...</p>;
}
return (
<table cellPadding='5'>
<thead>
<tr>
<td>Name</td>
<td>ID</td>
<td>Count</td>
<td>Bytes</td>
</tr>
</thead>
<tbody>
{this.orderedMessages()
.map((msg) => {
return (
<tr onClick={() => {this.onMessageSelected(msg.id)}}
key={msg.id}
className={cx('cabana-meta-messages-list-item', this.selectedMessageClass(msg.id))}>
<td>{msg.frame ? msg.frame.name : 'undefined'}</td>
<td>{msg.id}</td>
<td>{msg.entries.length}</td>
<td>
<MessageBytes
message={msg}
seekTime={this.props.seekTime}
maxByteStateChangeCount={this.props.maxByteStateChangeCount} />
</td>
</tr>
)
})}
</tbody>
</table>
);
}
timeWindow() {
const {route, currentParts} = this.props;
if(route) {
const partStartOffset = currentParts[0] * 60,
partEndOffset = (currentParts[1] + 1) * 60;
const windowStartTime = Moment(route.start_time).add(partStartOffset, 's').format('HH:mm:ss');
const windowEndTime = Moment(route.start_time).add(partEndOffset, 's').format('HH:mm:ss');
return `${windowStartTime} - ${windowEndTime}`;
} else return '';
}
shareUrl() {
const add = {max: this.props.route.proclog, url: this.props.route.url};
const remove = [GITHUB_AUTH_TOKEN_KEY]; // don't share github access
const shareUrl = modifyQueryParameters({add, remove})
return shareUrl;
}
render() {
return (
<div className='cabana-meta'>
<div className='cabana-meta-header'>
<h5 className='cabana-meta-header-label t-capline'>Currently editing:</h5>
<strong className='cabana-meta-header-filename'>{this.props.dbcFilename}</strong>
{this.props.dbcLastSaved !== null ?
<div className='cabana-meta-header-last-saved'>
<p>Last saved: {this.lastSavedPretty()}</p>
</div>
: null
}
<div className='cabana-meta-header-actions'>
<div className='cabana-meta-header-action'>
<button onClick={this.props.showLoadDbc}>Load DBC</button>
</div>
<div className='cabana-meta-header-action'
data-clipboard-text={this.shareUrl()}
data-clipboard-action='copy'
ref={(ref) => ref ? new Clipboard(ref) : null}>
<a className='button'
href={this.shareUrl()}
onClick={(e) => e.preventDefault()}>Copy Share Link</a>
</div>
<div className='cabana-meta-header-action'>
<button onClick={this.props.showSaveDbc}>Save DBC</button>
</div>
</div>
</div>
<div className='cabana-meta-messages'>
<div className='cabana-meta-messages-header'>
<h5 className='t-capline'>Available messages</h5>
</div>
<div className='cabana-meta-messages-window'>
<div className='cabana-meta-messages-filter'>
<div className='form-field form-field--small'>
<input type="text"
value={this.state.filterText}
onFocus={this.onFilterFocus}
onBlur={this.onFilterUnfocus}
onChange={this.onFilterChanged} />
</div>
</div>
<div className='cabana-meta-messages-list'>
{this.renderAvailableMessagesList()}
</div>
</div>
</div>
</div>
);
}
}