2017-05-29 17:52:17 -06:00
|
|
|
import React, { Component } from 'react';
|
2017-06-13 18:40:05 -06:00
|
|
|
import Moment from 'moment';
|
|
|
|
import PropTypes from 'prop-types';
|
2017-05-29 17:52:17 -06:00
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
import {USE_UNLOGGER, PART_SEGMENT_LENGTH, STREAMING_WINDOW} from './config';
|
2017-06-16 23:28:51 -06:00
|
|
|
import * as GithubAuth from './api/github-auth';
|
2017-07-27 04:03:12 -06:00
|
|
|
import cx from 'classnames';
|
|
|
|
|
2017-08-10 19:55:10 -06:00
|
|
|
import auth from './api/comma-auth';
|
2017-06-13 18:40:05 -06:00
|
|
|
import DBC from './models/can/dbc';
|
2017-08-08 15:30:59 -06:00
|
|
|
import Meta from './components/Meta';
|
|
|
|
import Explorer from './components/Explorer';
|
2017-06-13 18:40:05 -06:00
|
|
|
import * as Routes from './api/routes';
|
2017-08-03 15:41:52 -06:00
|
|
|
import OnboardingModal from './components/Modals/OnboardingModal';
|
2017-06-13 18:40:05 -06:00
|
|
|
import SaveDbcModal from './components/SaveDbcModal';
|
|
|
|
import LoadDbcModal from './components/LoadDbcModal';
|
|
|
|
const CanFetcher = require('./workers/can-fetcher.worker.js');
|
|
|
|
const MessageParser = require("./workers/message-parser.worker.js");
|
2017-06-19 00:00:33 -06:00
|
|
|
const CanOffsetFinder = require('./workers/can-offset-finder.worker.js');
|
2017-08-03 15:41:52 -06:00
|
|
|
const CanStreamerWorker = require('./workers/CanStreamerWorker.worker.js');
|
2017-06-19 17:35:10 -06:00
|
|
|
import debounce from './utils/debounce';
|
2017-06-19 21:40:07 -06:00
|
|
|
import EditMessageModal from './components/EditMessageModal';
|
2017-06-27 17:28:42 -06:00
|
|
|
import LoadingBar from './components/LoadingBar';
|
2017-08-09 14:03:20 -06:00
|
|
|
import {persistDbc,
|
|
|
|
fetchPersistedDbc,
|
|
|
|
unpersistGithubAuthToken} from './api/localstorage';
|
2017-08-08 15:30:59 -06:00
|
|
|
import OpenDbc from './api/OpenDbc';
|
2017-06-29 18:04:40 -06:00
|
|
|
import UnloggerClient from './api/unlogger';
|
2017-08-03 15:41:52 -06:00
|
|
|
import PandaReader from './api/panda-reader';
|
2017-07-20 19:42:08 -06:00
|
|
|
import * as ObjectUtils from './utils/object';
|
2017-07-20 20:24:52 -06:00
|
|
|
import {hash} from './utils/string';
|
2017-06-28 19:03:21 -06:00
|
|
|
|
2017-05-29 17:52:17 -06:00
|
|
|
export default class CanExplorer extends Component {
|
|
|
|
static propTypes = {
|
2017-08-07 16:53:21 -06:00
|
|
|
dongleId: PropTypes.string,
|
|
|
|
name: PropTypes.string,
|
2017-06-28 19:03:21 -06:00
|
|
|
dbc: PropTypes.instanceOf(DBC),
|
2017-06-28 19:39:52 -06:00
|
|
|
dbcFilename: PropTypes.string,
|
2017-07-05 16:57:03 -06:00
|
|
|
githubAuthToken: PropTypes.string,
|
2017-07-11 21:44:58 -06:00
|
|
|
autoplay: PropTypes.bool,
|
|
|
|
max: PropTypes.number,
|
|
|
|
url: PropTypes.string,
|
2017-05-29 17:52:17 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
messages: {},
|
2017-07-19 23:40:20 -06:00
|
|
|
selectedMessages: [],
|
2017-08-03 15:41:52 -06:00
|
|
|
route: null,
|
2017-08-10 19:55:10 -06:00
|
|
|
routes: [],
|
2017-06-13 18:40:05 -06:00
|
|
|
canFrameOffset: -1,
|
2017-07-12 15:01:00 -06:00
|
|
|
firstCanTime: 0,
|
2017-08-03 15:41:52 -06:00
|
|
|
lastBusTime: null,
|
2017-05-29 17:52:17 -06:00
|
|
|
selectedMessage: null,
|
2017-06-15 00:22:52 -06:00
|
|
|
currentParts: [0,0],
|
2017-08-03 15:41:52 -06:00
|
|
|
showOnboarding: false,
|
2017-06-13 18:40:05 -06:00
|
|
|
showLoadDbc: false,
|
|
|
|
showSaveDbc: false,
|
2017-06-19 21:40:07 -06:00
|
|
|
showEditMessageModal: false,
|
|
|
|
editMessageModalMessage: null,
|
2017-08-03 15:41:52 -06:00
|
|
|
dbc: (props.dbc ? props.dbc : new DBC()),
|
|
|
|
dbcText: (props.dbc ? props.dbc.text() : (new DBC()).text()),
|
|
|
|
dbcFilename: (props.dbcFilename ? props.dbcFilename : 'New_DBC'),
|
2017-06-22 21:38:38 -06:00
|
|
|
dbcLastSaved: null,
|
2017-06-23 15:17:35 -06:00
|
|
|
seekTime: 0,
|
|
|
|
seekIndex: 0,
|
2017-06-27 17:28:42 -06:00
|
|
|
maxByteStateChangeCount: 0,
|
2017-06-29 15:35:03 -06:00
|
|
|
isLoading: true,
|
|
|
|
partsLoaded: 0,
|
2017-07-27 04:03:12 -06:00
|
|
|
spawnWorkerHash: null,
|
2017-08-03 15:41:52 -06:00
|
|
|
attemptingPandaConnection: false,
|
|
|
|
pandaNoDeviceSelected: false,
|
2017-08-07 13:49:19 -06:00
|
|
|
live: false,
|
2017-08-09 14:03:20 -06:00
|
|
|
isGithubAuthenticated: props.githubAuthToken !== null && props.githubAuthToken !== undefined,
|
2017-05-29 17:52:17 -06:00
|
|
|
};
|
2017-08-09 14:03:20 -06:00
|
|
|
|
2017-06-28 21:28:39 -06:00
|
|
|
this.openDbcClient = new OpenDbc(props.githubAuthToken);
|
2017-06-29 21:23:35 -06:00
|
|
|
if(USE_UNLOGGER) {
|
|
|
|
this.unloggerClient = new UnloggerClient();
|
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
this.pandaReader = new PandaReader();
|
|
|
|
|
|
|
|
this.showOnboarding = this.showOnboarding.bind(this);
|
|
|
|
this.hideOnboarding = this.hideOnboarding.bind(this);
|
2017-06-13 18:40:05 -06:00
|
|
|
this.showLoadDbc = this.showLoadDbc.bind(this);
|
|
|
|
this.hideLoadDbc = this.hideLoadDbc.bind(this);
|
|
|
|
this.showSaveDbc = this.showSaveDbc.bind(this);
|
|
|
|
this.hideSaveDbc = this.hideSaveDbc.bind(this);
|
2017-06-19 21:40:07 -06:00
|
|
|
this.showEditMessageModal = this.showEditMessageModal.bind(this);
|
|
|
|
this.hideEditMessageModal = this.hideEditMessageModal.bind(this);
|
2017-06-13 18:40:05 -06:00
|
|
|
this.onDbcSelected = this.onDbcSelected.bind(this);
|
|
|
|
this.onDbcSaved = this.onDbcSaved.bind(this);
|
|
|
|
this.onConfirmedSignalChange = this.onConfirmedSignalChange.bind(this);
|
2017-06-15 00:22:52 -06:00
|
|
|
this.onPartChange = this.onPartChange.bind(this);
|
2017-06-20 17:46:07 -06:00
|
|
|
this.onMessageFrameEdited = this.onMessageFrameEdited.bind(this);
|
2017-06-23 15:17:35 -06:00
|
|
|
this.onSeek = this.onSeek.bind(this);
|
2017-06-29 18:04:40 -06:00
|
|
|
this.onUserSeek = this.onUserSeek.bind(this);
|
2017-06-23 17:06:34 -06:00
|
|
|
this.onMessageSelected = this.onMessageSelected.bind(this);
|
2017-06-29 15:35:03 -06:00
|
|
|
this.onMessageUnselected = this.onMessageUnselected.bind(this);
|
2017-06-28 19:03:21 -06:00
|
|
|
this.initCanData = this.initCanData.bind(this);
|
2017-07-19 23:40:20 -06:00
|
|
|
this.updateSelectedMessages = this.updateSelectedMessages.bind(this);
|
2017-08-03 15:41:52 -06:00
|
|
|
this.handlePandaConnect = this.handlePandaConnect.bind(this);
|
|
|
|
this.processStreamedCanMessages = this.processStreamedCanMessages.bind(this);
|
|
|
|
this.onStreamedCanMessagesProcessed = this.onStreamedCanMessagesProcessed.bind(this);
|
2017-07-27 04:03:12 -06:00
|
|
|
this.showingModal = this.showingModal.bind(this);
|
2017-08-03 15:41:52 -06:00
|
|
|
this.lastMessageEntriesById = this.lastMessageEntriesById.bind(this);
|
2017-08-09 14:03:20 -06:00
|
|
|
this.githubSignOut = this.githubSignOut.bind(this);
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
|
2017-06-13 18:40:05 -06:00
|
|
|
componentWillMount() {
|
2017-08-07 16:22:57 -06:00
|
|
|
const {dongleId, name} = this.props;
|
2017-08-03 15:41:52 -06:00
|
|
|
if(this.props.max && this.props.url) {
|
|
|
|
const {max, url} = this.props;
|
2017-08-03 17:56:59 -06:00
|
|
|
const {startTime} = Routes.parseRouteName(name);
|
|
|
|
|
|
|
|
const route = {fullname: name, proclog: max, url: url, start_time: startTime};
|
2017-08-03 15:41:52 -06:00
|
|
|
this.setState({route, currentParts: [0, Math.min(max - 1, PART_SEGMENT_LENGTH - 1)]}, this.initCanData);
|
2017-08-10 19:55:10 -06:00
|
|
|
} else if (auth.isAuthenticated() && !name) {
|
|
|
|
Routes.fetchRoutes().then((routes) => {
|
|
|
|
const _routes = [];
|
|
|
|
Object.keys(routes).forEach((route) => {
|
|
|
|
_routes.push(routes[route]);
|
|
|
|
})
|
|
|
|
this.setState({ routes: _routes });
|
|
|
|
if (!_routes[name]) {
|
|
|
|
this.showOnboarding();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else if(dongleId && name) {
|
2017-08-03 15:41:52 -06:00
|
|
|
Routes.fetchRoutes(dongleId).then((routes) => {
|
|
|
|
if(routes && routes[name]) {
|
|
|
|
const route = routes[name];
|
|
|
|
const newState = {route, currentParts: [0, Math.min(route.proclog - 1, PART_SEGMENT_LENGTH - 1)]};
|
|
|
|
this.setState(newState, this.initCanData);
|
|
|
|
} else {
|
|
|
|
this.showOnboarding();
|
2017-06-21 23:56:52 -06:00
|
|
|
}
|
2017-08-03 15:41:52 -06:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.showOnboarding();
|
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
|
2017-06-28 19:03:21 -06:00
|
|
|
initCanData() {
|
|
|
|
const {route} = this.state;
|
|
|
|
|
|
|
|
const offsetFinder = new CanOffsetFinder();
|
|
|
|
offsetFinder.postMessage({partCount: route.proclog,
|
|
|
|
base: route.url});
|
|
|
|
|
|
|
|
offsetFinder.onmessage = (e) => {
|
|
|
|
const {canFrameOffset, firstCanTime} = e.data;
|
|
|
|
|
|
|
|
this.setState({canFrameOffset, firstCanTime}, () => {
|
|
|
|
this.spawnWorker(this.state.currentParts);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-06-30 17:00:21 -06:00
|
|
|
onDbcSelected(dbcFilename, dbc) {
|
2017-08-07 16:22:57 -06:00
|
|
|
const {route} = this.state;
|
2017-06-13 18:40:05 -06:00
|
|
|
this.hideLoadDbc();
|
2017-08-21 19:24:51 -06:00
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
if(route) {
|
|
|
|
this.setState({dbc,
|
|
|
|
dbcFilename,
|
|
|
|
dbcText: dbc.text(),
|
|
|
|
partsLoaded: 0,
|
|
|
|
selectedMessage: null,
|
|
|
|
messages: {}}, () => {
|
|
|
|
|
|
|
|
// Pass DBC text to webworker b/c can't pass instance of es6 class
|
|
|
|
this.spawnWorker(this.state.currentParts);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.setState({dbc, dbcFilename, dbcText: dbc.text(), messages: {}});
|
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
|
2017-06-13 18:40:05 -06:00
|
|
|
onDbcSaved(dbcFilename) {
|
|
|
|
const dbcLastSaved = Moment();
|
|
|
|
this.setState({dbcLastSaved, dbcFilename})
|
|
|
|
this.hideSaveDbc();
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
addAndRehydrateMessages(newMessages, options) {
|
|
|
|
// Adds new message entries to messages state
|
|
|
|
// and "rehydrates" ES6 classes (message frame)
|
|
|
|
// lost from JSON serialization in webworker data cloning.
|
|
|
|
if(options === undefined) options = {};
|
|
|
|
|
|
|
|
const messages = {...this.state.messages};
|
|
|
|
for(var key in newMessages) {
|
|
|
|
// add message
|
|
|
|
if ((options.replace !== true) && key in messages) {
|
|
|
|
messages[key].entries = messages[key].entries.concat(newMessages[key].entries);
|
|
|
|
messages[key].byteStateChangeCounts = newMessages[key].byteStateChangeCounts;
|
|
|
|
} else {
|
|
|
|
messages[key] = newMessages[key];
|
|
|
|
messages[key].frame = this.state.dbc.messages.get(messages[key].address);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
2017-07-20 20:24:52 -06:00
|
|
|
spawnWorker(parts, options) {
|
|
|
|
// options is object of {part, prevMsgEntries, spawnWorkerHash, prepend}
|
2017-06-27 17:28:42 -06:00
|
|
|
if(!this.state.isLoading) {
|
|
|
|
this.setState({isLoading: true});
|
|
|
|
}
|
2017-06-15 00:22:52 -06:00
|
|
|
const [minPart, maxPart] = parts;
|
2017-07-20 20:24:52 -06:00
|
|
|
let part = minPart, prevMsgEntries = {}, prepend = false, spawnWorkerHash;
|
|
|
|
if(options) {
|
|
|
|
if(options.part) part = options.part;
|
|
|
|
if(options.prevMsgEntries) prevMsgEntries = options.prevMsgEntries;
|
|
|
|
if(options.spawnWorkerHash) {
|
|
|
|
spawnWorkerHash = options.spawnWorkerHash;
|
|
|
|
}
|
2017-06-15 00:22:52 -06:00
|
|
|
}
|
2017-07-20 20:24:52 -06:00
|
|
|
if(!spawnWorkerHash) {
|
|
|
|
spawnWorkerHash = hash(Math.random().toString(16));
|
|
|
|
this.setState({spawnWorkerHash});
|
|
|
|
}
|
|
|
|
|
2017-06-29 15:35:03 -06:00
|
|
|
if(part === minPart) {
|
|
|
|
this.setState({partsLoaded: 0});
|
|
|
|
}
|
2017-06-15 00:22:52 -06:00
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
const {dbc, dbcFilename, route, firstCanTime, canFrameOffset, maxByteStateChangeCount} = this.state;
|
2017-06-13 18:40:05 -06:00
|
|
|
var worker = new CanFetcher();
|
|
|
|
|
|
|
|
worker.onmessage = (e) => {
|
2017-07-20 20:24:52 -06:00
|
|
|
if(spawnWorkerHash !== this.state.spawnWorkerHash) {
|
2017-06-19 17:35:10 -06:00
|
|
|
// Parts changed, stop spawning workers.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-07 16:22:57 -06:00
|
|
|
if(this.state.dbcFilename !== dbcFilename) {
|
2017-06-16 23:28:51 -06:00
|
|
|
// DBC changed while this worker was running
|
|
|
|
// -- don't update messages and halt recursion.
|
|
|
|
return;
|
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
let {newMessages, maxByteStateChangeCount} = e.data;
|
2017-06-23 19:29:31 -06:00
|
|
|
if(maxByteStateChangeCount > this.state.maxByteStateChangeCount) {
|
2017-09-29 18:43:43 -06:00
|
|
|
this.setState({ maxByteStateChangeCount });
|
2017-08-03 15:41:52 -06:00
|
|
|
} else {
|
|
|
|
maxByteStateChangeCount = this.state.maxByteStateChangeCount;
|
2017-06-23 19:29:31 -06:00
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
const messages = this.addAndRehydrateMessages(newMessages, maxByteStateChangeCount);
|
2017-06-23 17:41:05 -06:00
|
|
|
const prevMsgEntries = {};
|
|
|
|
for(let key in newMessages) {
|
|
|
|
const msg = newMessages[key];
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-06-23 17:41:05 -06:00
|
|
|
prevMsgEntries[key] = msg.entries[msg.entries.length - 1];
|
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-06-13 18:40:05 -06:00
|
|
|
this.setState({messages,
|
|
|
|
partsLoaded: this.state.partsLoaded + 1}, () => {
|
2017-06-15 00:22:52 -06:00
|
|
|
if(part < maxPart) {
|
2017-07-20 20:24:52 -06:00
|
|
|
this.spawnWorker(parts, {part: part + 1, prevMsgEntries, spawnWorkerHash, prepend});
|
2017-06-27 17:28:42 -06:00
|
|
|
} else {
|
|
|
|
this.setState({isLoading: false});
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
})
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-06-16 23:28:51 -06:00
|
|
|
worker.postMessage({dbcText: dbc.text(),
|
|
|
|
base: route.url,
|
2017-06-13 18:40:05 -06:00
|
|
|
num: part,
|
2017-07-11 19:10:31 -06:00
|
|
|
canStartTime: firstCanTime - canFrameOffset,
|
2017-08-03 15:41:52 -06:00
|
|
|
prevMsgEntries,
|
|
|
|
maxByteStateChangeCount
|
2017-06-23 15:17:35 -06:00
|
|
|
});
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-07-27 04:03:12 -06:00
|
|
|
showingModal() {
|
|
|
|
const {
|
2017-08-03 15:41:52 -06:00
|
|
|
showOnboarding,
|
2017-07-27 04:03:12 -06:00
|
|
|
showLoadDbc,
|
|
|
|
showSaveDbc,
|
|
|
|
showAddSignal,
|
|
|
|
showEditMessageModal,
|
|
|
|
} = this.state;
|
2017-08-03 15:41:52 -06:00
|
|
|
return showOnboarding || showLoadDbc || showSaveDbc || showAddSignal || showEditMessageModal;
|
|
|
|
}
|
|
|
|
|
|
|
|
showOnboarding() {
|
|
|
|
this.setState({ showOnboarding: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
hideOnboarding() {
|
|
|
|
this.setState({ showOnboarding: false });
|
2017-07-27 04:03:12 -06:00
|
|
|
}
|
|
|
|
|
2017-06-13 18:40:05 -06:00
|
|
|
showLoadDbc() {
|
|
|
|
this.setState({showLoadDbc: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
hideLoadDbc() {
|
|
|
|
this.setState({showLoadDbc: false});
|
|
|
|
}
|
|
|
|
|
|
|
|
showSaveDbc() {
|
|
|
|
this.setState({showSaveDbc: true})
|
|
|
|
}
|
|
|
|
|
|
|
|
hideSaveDbc() {
|
|
|
|
this.setState({showSaveDbc: false})
|
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
reparseMessages(messages) {
|
|
|
|
this.setState({isLoading: true});
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
const {dbc} = this.state;
|
2017-06-13 18:40:05 -06:00
|
|
|
var worker = new MessageParser();
|
|
|
|
worker.onmessage = (e) => {
|
2017-08-03 15:41:52 -06:00
|
|
|
let messages = e.data;
|
|
|
|
messages = this.addAndRehydrateMessages(messages, {replace: true});
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-06-27 17:28:42 -06:00
|
|
|
this.setState({messages, isLoading: false})
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
worker.postMessage({messages,
|
2017-06-13 18:40:05 -06:00
|
|
|
dbcText: dbc.text(),
|
|
|
|
canStartTime: this.state.firstCanTime});
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
|
2017-08-03 17:56:59 -06:00
|
|
|
updateMessageFrame(messageId, frame) {
|
|
|
|
const {messages} = this.state;
|
|
|
|
|
|
|
|
messages[messageId].frame = frame;
|
|
|
|
this.setState({messages});
|
|
|
|
}
|
|
|
|
|
2017-08-21 19:24:51 -06:00
|
|
|
persistDbc({ dbcFilename, dbc }) {
|
|
|
|
const { route } = this.state;
|
|
|
|
if(route) {
|
|
|
|
persistDbc(route.fullname, { dbcFilename, dbc });
|
|
|
|
} else {
|
|
|
|
persistDbc('live', { dbcFilename, dbc });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
onConfirmedSignalChange(message, signals) {
|
|
|
|
const {dbc, dbcFilename, route} = this.state;
|
|
|
|
dbc.setSignals(message.address, {...signals});
|
|
|
|
|
2017-08-03 17:56:59 -06:00
|
|
|
this.updateMessageFrame(message.id, dbc.messages.get(message.address));
|
|
|
|
|
2017-08-21 19:24:51 -06:00
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
2017-08-03 15:41:52 -06:00
|
|
|
|
|
|
|
const messages = {};
|
|
|
|
const newMessage = {...message};
|
|
|
|
const frame = dbc.messages.get(message.address);
|
|
|
|
newMessage.frame = frame;
|
|
|
|
|
|
|
|
messages[message.id] = newMessage;
|
|
|
|
|
|
|
|
this.setState({dbc, dbcText: dbc.text()},
|
|
|
|
() => this.reparseMessages(messages));
|
|
|
|
}
|
|
|
|
|
2017-07-20 23:00:28 -06:00
|
|
|
partChangeDebounced = debounce(() => {
|
|
|
|
const {currentParts} = this.state;
|
|
|
|
this.spawnWorker(currentParts);
|
2017-07-07 16:06:47 -06:00
|
|
|
}, 500);
|
|
|
|
|
2017-07-05 08:56:24 -06:00
|
|
|
onPartChange(part) {
|
2017-08-07 16:22:57 -06:00
|
|
|
let {currentParts, canFrameOffset, route, messages} = this.state;
|
2017-09-29 19:40:47 -06:00
|
|
|
if (canFrameOffset === -1 || part + PART_SEGMENT_LENGTH > route.proclog) {
|
2017-07-05 11:37:48 -06:00
|
|
|
return
|
|
|
|
}
|
2017-07-07 16:06:47 -06:00
|
|
|
|
2017-07-20 20:24:52 -06:00
|
|
|
// determine new parts to load, whether to prepend or append
|
2017-06-27 16:12:18 -06:00
|
|
|
const currentPartSpan = currentParts[1] - currentParts[0] + 1;
|
2017-07-20 20:24:52 -06:00
|
|
|
|
|
|
|
// update current parts
|
2017-06-20 23:52:57 -06:00
|
|
|
currentParts = [part, part + currentPartSpan - 1];
|
2017-07-20 20:24:52 -06:00
|
|
|
|
|
|
|
// update messages to only preserve entries in new part range
|
2017-07-20 19:42:08 -06:00
|
|
|
const messagesKvPairs = Object.entries(messages)
|
|
|
|
.map(([messageId, message]) =>
|
|
|
|
[messageId, {...message,
|
2017-07-20 23:00:28 -06:00
|
|
|
entries: []
|
2017-07-20 19:42:08 -06:00
|
|
|
}
|
|
|
|
]);
|
|
|
|
messages = ObjectUtils.fromArray(messagesKvPairs);
|
|
|
|
|
2017-07-20 20:24:52 -06:00
|
|
|
// update state then load new parts
|
2017-07-20 23:00:28 -06:00
|
|
|
this.setState({currentParts, messages, seekTime: part * 60}, this.partChangeDebounced);
|
2017-07-05 08:56:24 -06:00
|
|
|
}
|
2017-06-15 00:22:52 -06:00
|
|
|
|
2017-06-19 21:40:07 -06:00
|
|
|
showEditMessageModal(msgKey) {
|
2017-06-20 17:46:07 -06:00
|
|
|
const msg = this.state.messages[msgKey];
|
|
|
|
if(!msg.frame) {
|
|
|
|
msg.frame = this.state.dbc.createFrame(msg.address);
|
|
|
|
}
|
|
|
|
|
2017-06-30 21:12:47 -06:00
|
|
|
|
2017-06-19 21:40:07 -06:00
|
|
|
this.setState({showEditMessageModal: true,
|
2017-06-20 17:46:07 -06:00
|
|
|
editMessageModalMessage: msgKey,
|
2017-08-03 15:41:52 -06:00
|
|
|
messages: this.state.messages,
|
2017-08-05 16:14:55 -06:00
|
|
|
dbcText: this.state.dbc.text()});
|
2017-06-19 21:40:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
hideEditMessageModal() {
|
|
|
|
this.setState({showEditMessageModal: false});
|
|
|
|
}
|
|
|
|
|
2017-06-20 17:46:07 -06:00
|
|
|
onMessageFrameEdited(messageFrame) {
|
2017-06-28 19:03:21 -06:00
|
|
|
const {messages,
|
|
|
|
route,
|
|
|
|
dbcFilename,
|
|
|
|
dbc,
|
|
|
|
editMessageModalMessage} = this.state;
|
|
|
|
|
|
|
|
const message = Object.assign({}, messages[editMessageModalMessage]);
|
2017-06-19 21:40:07 -06:00
|
|
|
message.frame = messageFrame;
|
2017-07-04 19:21:36 -06:00
|
|
|
dbc.messages.set(messageFrame.id, messageFrame);
|
2017-08-21 19:24:51 -06:00
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
|
|
|
|
2017-06-20 17:46:07 -06:00
|
|
|
|
2017-06-28 19:03:21 -06:00
|
|
|
messages[editMessageModalMessage] = message;
|
2017-08-03 15:41:52 -06:00
|
|
|
this.setState({messages, dbc, dbcText: dbc.text()});
|
2017-06-19 21:40:07 -06:00
|
|
|
this.hideEditMessageModal();
|
|
|
|
}
|
|
|
|
|
2017-06-23 15:17:35 -06:00
|
|
|
onSeek(seekIndex, seekTime) {
|
|
|
|
this.setState({seekIndex, seekTime});
|
|
|
|
}
|
|
|
|
|
2017-06-29 18:04:40 -06:00
|
|
|
onUserSeek(seekTime) {
|
|
|
|
if(USE_UNLOGGER) {
|
|
|
|
this.unloggerClient.seek(this.props.dongleId, this.props.name, seekTime);
|
|
|
|
}
|
2017-07-04 19:15:58 -06:00
|
|
|
|
|
|
|
const msg = this.state.messages[this.state.selectedMessage];
|
|
|
|
let seekIndex;
|
|
|
|
if(msg) {
|
|
|
|
seekIndex = msg.entries.findIndex((e) => e.relTime >= seekTime);
|
|
|
|
if(seekIndex === -1) {
|
|
|
|
seekIndex = 0
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
seekIndex = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({seekIndex, seekTime});
|
2017-06-29 18:04:40 -06:00
|
|
|
}
|
|
|
|
|
2017-06-23 15:17:35 -06:00
|
|
|
onMessageSelected(msgKey) {
|
|
|
|
let {seekTime, seekIndex, messages} = this.state;
|
|
|
|
const msg = messages[msgKey];
|
2017-06-23 17:06:34 -06:00
|
|
|
|
2017-07-20 23:00:28 -06:00
|
|
|
if(seekTime > 0 && msg.entries.length > 0) {
|
2017-06-23 15:17:35 -06:00
|
|
|
seekIndex = msg.entries.findIndex((e) => e.relTime >= seekTime);
|
|
|
|
if(seekIndex === -1) {
|
|
|
|
seekIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
seekTime = msg.entries[seekIndex].relTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({seekTime, seekIndex, selectedMessage: msgKey});
|
2017-06-22 21:38:38 -06:00
|
|
|
}
|
|
|
|
|
2017-07-19 23:40:20 -06:00
|
|
|
updateSelectedMessages(selectedMessages) {
|
|
|
|
this.setState({selectedMessages});
|
|
|
|
}
|
|
|
|
|
2017-06-29 15:35:03 -06:00
|
|
|
onMessageUnselected(msgKey) {
|
|
|
|
this.setState({selectedMessage: null});
|
|
|
|
}
|
|
|
|
|
2017-06-30 22:27:04 -06:00
|
|
|
loginWithGithub() {
|
2017-08-03 15:41:52 -06:00
|
|
|
const {route} = this.state;
|
2017-07-19 23:40:20 -06:00
|
|
|
return (
|
2017-08-03 15:41:52 -06:00
|
|
|
<a href={GithubAuth.authorizeUrl(route && route.fullname ? route.fullname : '')}
|
2017-07-27 04:03:12 -06:00
|
|
|
className='button button--dark button--inline'>
|
2017-07-19 23:40:20 -06:00
|
|
|
<i className='fa fa-github'></i>
|
|
|
|
<span> Log in with Github</span>
|
|
|
|
</a>
|
|
|
|
)
|
2017-06-30 22:27:04 -06:00
|
|
|
}
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
lastMessageEntriesById(obj, [msgId, message]) {
|
|
|
|
obj[msgId] = message.entries[message.entries.length - 1];
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
processStreamedCanMessages(newCanMessages) {
|
|
|
|
const {dbcText} = this.state;
|
|
|
|
const {firstCanTime, lastBusTime, messages, maxByteStateChangeCount} = this.state;
|
|
|
|
// map msg id to arrays
|
|
|
|
const prevMsgEntries = Object.entries(messages).reduce(this.lastMessageEntriesById, {});
|
|
|
|
|
|
|
|
const byteStateChangeCountsByMessage = Object.entries(messages).reduce(
|
|
|
|
(obj, [msgId, msg]) => {
|
|
|
|
obj[msgId] = msg.byteStateChangeCounts
|
|
|
|
return obj;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
this.canStreamerWorker.postMessage({ newCanMessages,
|
|
|
|
prevMsgEntries,
|
|
|
|
firstCanTime,
|
|
|
|
dbcText,
|
|
|
|
lastBusTime,
|
|
|
|
byteStateChangeCountsByMessage,
|
|
|
|
maxByteStateChangeCount });
|
|
|
|
}
|
|
|
|
|
|
|
|
firstEntryIndexInsideStreamingWindow(entries) {
|
|
|
|
const lastEntryTime = entries[entries.length - 1].relTime;
|
|
|
|
const windowFloor = lastEntryTime - STREAMING_WINDOW;
|
|
|
|
|
|
|
|
for(let i = 0; i < entries.length; i++) {
|
|
|
|
if(entries[i].relTime > windowFloor) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
enforceStreamingMessageWindow(messages) {
|
|
|
|
let messageIds = Object.keys(messages);
|
|
|
|
for(let i = 0; i < messageIds.length; i++) {
|
|
|
|
const messageId = messageIds[i];
|
|
|
|
const message = messages[messageId];
|
|
|
|
if(message.entries.length < 2) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const lastEntryTime = message.entries[message.entries.length - 1].relTime;
|
|
|
|
const entrySpan = lastEntryTime - message.entries[0].relTime;
|
|
|
|
if(entrySpan > STREAMING_WINDOW) {
|
|
|
|
const newEntryFloor = this.firstEntryIndexInsideStreamingWindow(message.entries);
|
|
|
|
message.entries = message.entries.slice(newEntryFloor);
|
|
|
|
messages[messageId] = message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
_onStreamedCanMessagesProcessed(data) {
|
2017-08-07 16:22:57 -06:00
|
|
|
let {newMessages, seekTime, lastBusTime, firstCanTime, maxByteStateChangeCount} = data;
|
2017-08-03 15:41:52 -06:00
|
|
|
|
|
|
|
if(maxByteStateChangeCount < this.state.maxByteStateChangeCount) {
|
|
|
|
maxByteStateChangeCount = this.state.maxByteStateChangeCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
let messages = this.addAndRehydrateMessages(newMessages);
|
|
|
|
messages = this.enforceStreamingMessageWindow(messages);
|
|
|
|
let {seekIndex, selectedMessages} = this.state;
|
2017-10-26 13:21:10 -06:00
|
|
|
if (selectedMessages.length > 0 && messages[selectedMessages[0]] !== undefined) {
|
2017-08-03 15:41:52 -06:00
|
|
|
seekIndex = messages[selectedMessages[0]].entries.length - 1;
|
|
|
|
}
|
|
|
|
this.setState({messages, seekTime, seekIndex, lastBusTime, firstCanTime, maxByteStateChangeCount});
|
|
|
|
}
|
|
|
|
|
|
|
|
onStreamedCanMessagesProcessed(e) {
|
|
|
|
this._onStreamedCanMessagesProcessed(e.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
handlePandaConnect(e) {
|
|
|
|
this.setState({ attemptingPandaConnection: true, live: true });
|
|
|
|
|
|
|
|
const persistedDbc = fetchPersistedDbc('live');
|
2017-08-21 19:19:29 -06:00
|
|
|
if( persistedDbc ) {
|
2017-08-03 15:41:52 -06:00
|
|
|
const {dbc, dbcText} = persistedDbc;
|
|
|
|
this.setState({dbc, dbcText});
|
|
|
|
}
|
|
|
|
this.canStreamerWorker = new CanStreamerWorker();
|
|
|
|
this.canStreamerWorker.onmessage = this.onStreamedCanMessagesProcessed;
|
|
|
|
this.pandaReader.setOnMessagesReceivedCallback(this.processStreamedCanMessages);
|
|
|
|
this.pandaReader.connect().then(() => {
|
|
|
|
this.pandaReader.readLoop();
|
|
|
|
this.setState({ attemptingPandaConnection: false });
|
|
|
|
this.setState({ showOnboarding: false });
|
|
|
|
this.setState({ showLoadDbc: true });
|
|
|
|
}).catch((err) => {
|
2017-08-21 19:19:29 -06:00
|
|
|
console.log(err);
|
|
|
|
this.setState({ attemptingPandaConnection: false });
|
2017-08-03 15:41:52 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-09 14:03:20 -06:00
|
|
|
githubSignOut(e) {
|
|
|
|
unpersistGithubAuthToken();
|
|
|
|
this.setState({isGithubAuthenticated: false});
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
2017-08-03 17:56:59 -06:00
|
|
|
|
2017-05-29 17:52:17 -06:00
|
|
|
render() {
|
2017-07-19 23:40:20 -06:00
|
|
|
return (
|
2017-07-27 04:03:12 -06:00
|
|
|
<div id='cabana' className={ cx({ 'is-showing-modal': this.showingModal() }) }>
|
2017-07-19 23:40:20 -06:00
|
|
|
{this.state.isLoading ?
|
|
|
|
<LoadingBar
|
|
|
|
isLoading={this.state.isLoading}
|
|
|
|
/> : null}
|
|
|
|
<div className='cabana-header'>
|
|
|
|
<a className='cabana-header-logo' href=''>Comma Cabana</a>
|
|
|
|
<div className='cabana-header-account'>
|
2017-08-09 14:03:20 -06:00
|
|
|
{this.state.isGithubAuthenticated ?
|
|
|
|
<div>
|
|
|
|
<p>GitHub Authenticated</p>
|
|
|
|
<p className='cabana-header-account-signout'
|
|
|
|
onClick={this.githubSignOut}>Sign out</p>
|
|
|
|
</div>
|
2017-07-19 23:40:20 -06:00
|
|
|
: this.loginWithGithub()
|
|
|
|
}
|
2017-06-20 16:46:19 -06:00
|
|
|
</div>
|
2017-07-19 23:40:20 -06:00
|
|
|
</div>
|
|
|
|
<div className='cabana-window'>
|
2017-08-03 15:41:52 -06:00
|
|
|
<Meta url={this.state.route ? this.state.route.url : null}
|
2017-07-19 23:40:20 -06:00
|
|
|
messages={this.state.messages}
|
|
|
|
selectedMessages={this.state.selectedMessages}
|
|
|
|
updateSelectedMessages={this.updateSelectedMessages}
|
|
|
|
showEditMessageModal={this.showEditMessageModal}
|
|
|
|
currentParts={this.state.currentParts}
|
|
|
|
onMessageSelected={this.onMessageSelected}
|
|
|
|
onMessageUnselected={this.onMessageUnselected}
|
|
|
|
showLoadDbc={this.showLoadDbc}
|
|
|
|
showSaveDbc={this.showSaveDbc}
|
|
|
|
dbcFilename={this.state.dbcFilename}
|
|
|
|
dbcLastSaved={this.state.dbcLastSaved}
|
|
|
|
dongleId={this.props.dongleId}
|
|
|
|
name={this.props.name}
|
|
|
|
route={this.state.route}
|
|
|
|
seekTime={this.state.seekTime}
|
2017-08-03 15:41:52 -06:00
|
|
|
seekIndex={this.state.seekIndex}
|
2017-07-19 23:40:20 -06:00
|
|
|
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
|
|
|
isDemo={this.props.isDemo}
|
2017-08-03 15:41:52 -06:00
|
|
|
live={this.state.live}
|
2017-07-19 23:40:20 -06:00
|
|
|
/>
|
2017-08-03 15:41:52 -06:00
|
|
|
{this.state.route || this.state.live ?
|
2017-07-19 23:40:20 -06:00
|
|
|
<Explorer
|
2017-08-03 15:41:52 -06:00
|
|
|
url={this.state.route ? this.state.route.url : null}
|
|
|
|
live={this.state.live}
|
2017-07-19 23:40:20 -06:00
|
|
|
messages={this.state.messages}
|
|
|
|
selectedMessage={this.state.selectedMessage}
|
|
|
|
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
|
|
|
onSeek={this.onSeek}
|
|
|
|
onUserSeek={this.onUserSeek}
|
|
|
|
canFrameOffset={this.state.canFrameOffset}
|
|
|
|
firstCanTime={this.state.firstCanTime}
|
|
|
|
seekTime={this.state.seekTime}
|
|
|
|
seekIndex={this.state.seekIndex}
|
|
|
|
currentParts={this.state.currentParts}
|
|
|
|
partsLoaded={this.state.partsLoaded}
|
|
|
|
autoplay={this.props.autoplay}
|
|
|
|
showEditMessageModal={this.showEditMessageModal}
|
|
|
|
onPartChange={this.onPartChange}
|
2017-08-04 16:36:15 -06:00
|
|
|
routeStartTime={this.state.route ? this.state.route.start_time : Moment()}
|
2017-08-03 15:41:52 -06:00
|
|
|
partsCount={this.state.route ? this.state.route.proclog : 0}
|
2017-07-19 23:40:20 -06:00
|
|
|
/>
|
|
|
|
: null}
|
|
|
|
</div>
|
|
|
|
|
2017-08-03 15:41:52 -06:00
|
|
|
{ this.state.showOnboarding ?
|
|
|
|
<OnboardingModal
|
|
|
|
handlePandaConnect={ this.handlePandaConnect }
|
2017-08-07 16:22:57 -06:00
|
|
|
attemptingPandaConnection={ this.state.attemptingPandaConnection }
|
2017-08-10 19:55:10 -06:00
|
|
|
routes={ this.state.routes }
|
2017-08-03 15:41:52 -06:00
|
|
|
/> : null }
|
|
|
|
|
2017-07-27 04:03:12 -06:00
|
|
|
{this.state.showLoadDbc ?
|
|
|
|
<LoadDbcModal
|
|
|
|
onDbcSelected={this.onDbcSelected}
|
|
|
|
handleClose={this.hideLoadDbc}
|
|
|
|
openDbcClient={this.openDbcClient}
|
|
|
|
loginWithGithub={this.loginWithGithub()}
|
|
|
|
/> : null}
|
|
|
|
|
|
|
|
{this.state.showSaveDbc ?
|
|
|
|
<SaveDbcModal
|
|
|
|
dbc={this.state.dbc}
|
|
|
|
sourceDbcFilename={this.state.dbcFilename}
|
|
|
|
onDbcSaved={this.onDbcSaved}
|
|
|
|
handleClose={this.hideSaveDbc}
|
|
|
|
openDbcClient={this.openDbcClient}
|
|
|
|
hasGithubAuth={this.props.githubAuthToken !== null}
|
|
|
|
loginWithGithub={this.loginWithGithub()}
|
|
|
|
/> : null}
|
|
|
|
|
2017-07-19 23:40:20 -06:00
|
|
|
{this.state.showEditMessageModal ?
|
|
|
|
<EditMessageModal
|
2017-07-27 04:03:12 -06:00
|
|
|
handleClose={this.hideEditMessageModal}
|
|
|
|
handleSave={this.onMessageFrameEdited}
|
|
|
|
message={this.state.messages[this.state.editMessageModalMessage]}
|
|
|
|
/> : null}
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-07-19 23:40:20 -06:00
|
|
|
</div>
|
|
|
|
);
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
|
|
|
}
|