diff --git a/craco.config.js b/craco.config.js index d121e0a..0a45d5b 100644 --- a/craco.config.js +++ b/craco.config.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const WorkerLoaderPlugin = require('craco-worker-loader'); const SentryPlugin = require('craco-sentry-plugin'); diff --git a/package.json b/package.json index b1d35d4..281ee1d 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "build:staging": "env-cmd .env.staging craco build", "test": "craco test --env=jsdom", "test-ci": "CI=true craco test --env=jsdom", + "test-coverage": "CI=true craco test --env=jsdom --coverage", "netlify-sass": "node-sass src/index.scss > src/index.css", "sass": "node-sass src/index.scss -o src && node-sass -w src/index.scss -o src", "deploy": "npm run build && gh-pages -d build" @@ -123,7 +124,8 @@ "^@commaai/pandajs$": "/node_modules/@commaai/pandajs/lib/index.js", "^@commaai/hls.js$": "/node_modules/@commaai/hls.js/dist/hls.js", "^@commaai/(.*comma.*)$": "/node_modules/@commaai/$1/dist/index.js", - "^capnp-split$": "/node_modules/capnp-split/dist/index.js" + "^capnp-split$": "/node_modules/capnp-split/dist/index.js", + "\\.worker": "/src/__mocks__/workerMock.js" } }, "browserslist": { diff --git a/src/CanExplorer.js b/src/CanExplorer.js index 1fee46b..5940fd0 100644 --- a/src/CanExplorer.js +++ b/src/CanExplorer.js @@ -34,10 +34,10 @@ import * as ObjectUtils from './utils/object'; import { hash } from './utils/string'; import { modifyQueryParameters } from './utils/url'; -const RLogDownloader = require('./workers/rlog-downloader.worker.js'); -const LogCSVDownloader = require('./workers/dbc-csv-downloader.worker.js'); -const MessageParser = require('./workers/message-parser.worker.js'); -const CanStreamerWorker = require('./workers/CanStreamerWorker.worker.js'); +const RLogDownloader = require('./workers/rlog-downloader.worker'); +const LogCSVDownloader = require('./workers/dbc-csv-downloader.worker'); +const MessageParser = require('./workers/message-parser.worker'); +const CanStreamerWorker = require('./workers/CanStreamerWorker.worker'); export default class CanExplorer extends Component { constructor(props) { @@ -65,7 +65,7 @@ export default class CanExplorer extends Component { dbcText: props.dbc ? props.dbc.text() : new DBC().text(), dbcFilename: props.dbcFilename ? props.dbcFilename : 'New_DBC', dbcLastSaved: null, - seekTime: 0, + seekTime: props.seekTime || 0, seekIndex: 0, maxByteStateChangeCount: 0, isLoading: true, @@ -120,7 +120,7 @@ export default class CanExplorer extends Component { this.pandaReader.onMessage(this.processStreamedCanMessages); } - componentWillMount() { + componentDidMount() { const { dongleId, name } = this.props; if (CommaAuth.isAuthenticated() && !name) { this.showOnboarding(); @@ -995,6 +995,8 @@ export default class CanExplorer extends Component { partsLoaded } = this.state; + const { startTime, segments } = this.props; + return (
{ - const canExplorer = shallow(); +global.document.querySelector = jest.fn(); + +describe('CanExplorer', () => { + const props = { + max: 12, + url: 'https://chffrprivate.blob.core.windows.net/chffrprivate3-permanent/v2/cb38263377b873ee/78392b99580c5920227cc5b43dff8a70_2017-06-12--18-51-47', + name: '2017-06-12--18-51-47', + dongleId: 'cb38263377b873ee', + dbc: AcuraDbc, + isDemo: true, + dbcFilename: 'acura_ilx_2016_can.dbc', + autoplay: true + }; + + it('renders', () => { + /* + dongleId: PropTypes.string, + name: PropTypes.string, + dbc: PropTypes.instanceOf(DBC), + dbcFilename: PropTypes.string, + githubAuthToken: PropTypes.string, + autoplay: PropTypes.bool, + max: PropTypes.number, + url: PropTypes.string, + startTime: PropTypes.number, + segments: PropTypes.array + */ + const canExplorer = mount(); + expect(canExplorer.exists()).toBe(true); + expect(canExplorer.find(Explorer).length).toBe(1); + canExplorer.unmount(); + }); + + it('passes props to Explorer', () => { + const canExplorer = mount(); + expect(canExplorer.find(Explorer).length).toBe(1); + expect(canExplorer.exists()).toBe(true); + expect(canExplorer.find(Explorer).prop('startSegments')).toBe(canExplorer.prop('segments')); + expect(canExplorer.find(Explorer).prop('startTime')).toBe(canExplorer.prop('startTime')); + canExplorer.unmount(); + }); }); diff --git a/src/components/Explorer.js b/src/components/Explorer.js index d59d0a8..b341908 100644 --- a/src/components/Explorer.js +++ b/src/components/Explorer.js @@ -9,38 +9,75 @@ import RouteVideoSync from './RouteVideoSync'; import CanLog from './CanLog'; import Entries from '../models/can/entries'; import debounce from '../utils/debounce'; -import PartSelector from './PartSelector'; import PlaySpeedSelector from './PlaySpeedSelector'; +function clipSegment(_segment, _segmentIndices, nextMessage) { + let segment = _segment; + let segmentIndices = _segmentIndices; + if (segment.length === 2) { + const segmentStartIdx = nextMessage.entries.findIndex( + (e) => e.relTime >= segment[0] + ); + let segmentEndIdx = nextMessage.entries.findIndex( + (e) => e.relTime >= segment[1] + ); + if (segmentStartIdx !== -1) { + if (segmentEndIdx === -1) { + // previous segment end is past bounds of this message + segmentEndIdx = nextMessage.entries.length - 1; + } + const segmentStartTime = nextMessage.entries[segmentStartIdx].relTime; + const segmentEndTime = nextMessage.entries[segmentEndIdx].relTime; + + segment = [segmentStartTime, segmentEndTime]; + segmentIndices = [segmentStartIdx, segmentEndIdx]; + } else { + // segment times are out of boudns for this message + segment = []; + segmentIndices = []; + } + } + + return { segment, segmentIndices }; +} + export default class Explorer extends Component { - static propTypes = { - selectedMessage: PropTypes.string, - url: PropTypes.string, - live: PropTypes.bool.isRequired, - messages: PropTypes.objectOf(PropTypes.object), - onConfirmedSignalChange: PropTypes.func.isRequired, - canFrameOffset: PropTypes.number, - firstCanTime: PropTypes.number, - onSeek: PropTypes.func.isRequired, - autoplay: PropTypes.bool.isRequired, - onPartChange: PropTypes.func.isRequired, - partsCount: PropTypes.number - }; + updateSegment = debounce((messageId, _segment) => { + let segment = _segment; + const { messages, selectedMessage, currentParts } = this.props; + const { entries } = messages[selectedMessage]; + let segmentIndices = Entries.findSegmentIndices(entries, segment, true); + + // console.log(this.state.segment, '->', segment, segmentIndices); + if ( + segment[0] === currentParts[0] * 60 + && segment[1] === (currentParts[1] + 1) * 60 + ) { + segment = []; + segmentIndices = []; + } + this.setState({ + segment, + segmentIndices, + userSeekIndex: segmentIndices[0], + userSeekTime: segment[0] || 0 + }); + }, 250); constructor(props) { super(props); this.state = { plottedSignals: [], - segment: [], + segment: props.startSegments || [], segmentIndices: [], shouldShowAddSignal: true, userSeekIndex: 0, userSeekTime: 0, playing: props.autoplay, - signals: {}, playSpeed: 1 }; + this.onSignalPlotPressed = this.onSignalPlotPressed.bind(this); this.onSignalUnplotPressed = this.onSignalUnplotPressed.bind(this); this.onSegmentChanged = this.onSegmentChanged.bind(this); @@ -52,61 +89,33 @@ export default class Explorer extends Component { this.onPause = this.onPause.bind(this); this.onVideoClick = this.onVideoClick.bind(this); this.onSignalPlotChange = this.onSignalPlotChange.bind(this); - this._onKeyDown = this._onKeyDown.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); this.mergePlots = this.mergePlots.bind(this); this.toggleShouldShowAddSignal = this.toggleShouldShowAddSignal.bind(this); this.changePlaySpeed = this.changePlaySpeed.bind(this); } - _onKeyDown(e) { + componentDidMount() { + document.addEventListener('keydown', this.onKeyDown); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + } + + onKeyDown(e) { if (e.keyCode === 27) { // escape this.resetSegment(); } } - componentWillMount() { - document.addEventListener('keydown', this._onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this._onKeyDown); - } - - clipSegment(segment, segmentIndices, nextMessage) { - if (segment.length === 2) { - const segmentStartIdx = nextMessage.entries.findIndex( - (e) => e.relTime >= segment[0] - ); - let segmentEndIdx = nextMessage.entries.findIndex( - (e) => e.relTime >= segment[1] - ); - if (segmentStartIdx !== -1) { - if (segmentEndIdx === -1) { - // previous segment end is past bounds of this message - segmentEndIdx = nextMessage.entries.length - 1; - } - const segmentStartTime = nextMessage.entries[segmentStartIdx].relTime; - const segmentEndTime = nextMessage.entries[segmentEndIdx].relTime; - - segment = [segmentStartTime, segmentEndTime]; - segmentIndices = [segmentStartIdx, segmentEndIdx]; - } else { - // segment times are out of boudns for this message - segment = []; - segmentIndices = []; - } - } - - return { segment, segmentIndices }; - } - componentWillReceiveProps(nextProps) { const nextMessage = nextProps.messages[nextProps.selectedMessage]; const curMessage = this.props.messages[this.props.selectedMessage]; let { plottedSignals } = this.state; - if (Object.keys(nextProps.messages).length === 0) { + if (Object.keys(nextProps.messages).length === 0 && Object.keys(this.props.messages).length !== 0) { this.resetSegment(); } if (nextMessage && nextMessage.frame && nextMessage !== curMessage) { @@ -142,7 +151,7 @@ export default class Explorer extends Component { // by finding a entry indices // corresponding to old message segment/seek times. - const { segment, segmentIndices } = this.clipSegment( + const { segment, segmentIndices } = clipSegment( this.state.segment, this.state.segmentIndices, nextMessage @@ -171,7 +180,7 @@ export default class Explorer extends Component { && curMessage && nextMessage.entries.length !== curMessage.entries.length ) { - const { segment, segmentIndices } = this.clipSegment( + const { segment, segmentIndices } = clipSegment( this.state.segment, this.state.segmentIndices, nextMessage @@ -227,26 +236,6 @@ export default class Explorer extends Component { this.setState({ plottedSignals: newPlottedSignals }); } - updateSegment = debounce((messageId, segment) => { - const { entries } = this.props.messages[this.props.selectedMessage]; - let segmentIndices = Entries.findSegmentIndices(entries, segment, true); - - // console.log(this.state.segment, '->', segment, segmentIndices); - if ( - segment[0] === this.props.currentParts[0] * 60 - && segment[1] === (this.props.currentParts[1] + 1) * 60 - ) { - segment = []; - segmentIndices = []; - } - this.setState({ - segment, - segmentIndices, - userSeekIndex: segmentIndices[0], - userSeekTime: segment[0] || 0 - }); - }, 250); - onSegmentChanged(messageId, segment) { if (Array.isArray(segment)) { this.updateSegment(messageId, segment); @@ -255,7 +244,6 @@ export default class Explorer extends Component { resetSegment() { const { segment, segmentIndices } = this.state; - const { messages, selectedMessage } = this.props; if (segment.length > 0 || segmentIndices.length > 0) { // console.log(this.state.segment, '->', segment, segmentIndices); this.setState({ @@ -460,7 +448,7 @@ export default class Explorer extends Component { ? 'is-expanded' : null; - const { thumbnails, messages } = this.props; + const { thumbnails, messages, startTime } = this.props; let graphSegment = this.state.segment; if (!graphSegment.length && this.props.currentParts) { @@ -489,6 +477,7 @@ export default class Explorer extends Component { { paths = paths.filter((path) => path.indexOf('.dbc') !== -1); this.setState({ paths }); diff --git a/src/components/Meta.js b/src/components/Meta.js index daeb847..44d557e 100644 --- a/src/components/Meta.js +++ b/src/components/Meta.js @@ -54,7 +54,7 @@ export default class Meta extends Component { }; } - componentWillMount() { + componentDidMount() { this.lastSavedTimer = setInterval(() => { if (this.props.dbcLastSaved !== null) { this.setState({ lastSaved: this.props.dbcLastSaved.fromNow() }); diff --git a/src/components/RouteVideoSync.js b/src/components/RouteVideoSync.js index 2256c49..0b6167e 100644 --- a/src/components/RouteVideoSync.js +++ b/src/components/RouteVideoSync.js @@ -205,7 +205,8 @@ export default class RouteVideoSync extends Component { const { isLoading, shouldRestartHls, - shouldShowJpeg + shouldShowJpeg, + videoElement } = this.state; const { userSeekTime, @@ -213,7 +214,8 @@ export default class RouteVideoSync extends Component { playSpeed, playing, onVideoClick, - segmentIndices + segmentIndices, + startTime } = this.props; return (
@@ -231,7 +233,7 @@ export default class RouteVideoSync extends Component { url, process.env.REACT_APP_VIDEO_CDN ).getRearCameraStreamIndexUrl()} - startTime={this.startTime()} + startTime={startTime || 0} videoLength={this.videoLength()} playbackSpeed={playSpeed} onVideoElementAvailable={this.onVideoElementAvailable} @@ -254,7 +256,7 @@ export default class RouteVideoSync extends Component { segmentIndices={segmentIndices} onUserSeek={this.onUserSeek} onPlaySeek={this.onPlaySeek} - videoElement={this.state.videoElement} + videoElement={videoElement} onPlay={this.props.onPlay} onPause={this.props.onPause} playing={this.props.playing} @@ -281,4 +283,5 @@ RouteVideoSync.propTypes = { playSpeed: PropTypes.number.isRequired, onVideoClick: PropTypes.func, segmentIndices: PropTypes.array, + startTime: PropTypes.number }; diff --git a/src/index.js b/src/index.js index 6c070c6..5133e79 100644 --- a/src/index.js +++ b/src/index.js @@ -18,7 +18,19 @@ Sentry.init(); const routeFullName = getUrlParameter('route'); const isDemo = !routeFullName; -const props = { autoplay: true, isDemo }; +let segments = getUrlParameter('segments'); +if (segments && segments.length) { + segments = segments.split(',').map(Number); +} +if (segments.length !== 2) { + segments = undefined; +} +const props = { + autoplay: true, + startTime: Number(getUrlParameter('seekTime') || 0), + segments, + isDemo +}; let persistedDbc = null; if (routeFullName) { @@ -96,7 +108,7 @@ async function init() { if (token) { Request.configure(token); } - ReactDOM.render(, document.getElementById('root')); + ReactDOM.render(, document.getElementById('root')); // eslint-disable-line react/jsx-props-no-spreading } if (routeFullName || isDemo) { diff --git a/src/logging/Sentry.js b/src/logging/Sentry.js index dff2dcf..4f3e579 100644 --- a/src/logging/Sentry.js +++ b/src/logging/Sentry.js @@ -3,9 +3,10 @@ import Raven from 'raven-js'; function init() { if (process.env.NODE_ENV === 'production') { const opts = {}; + const webpackHash = __webpack_hash__; // eslint-disable-line - if (typeof __webpack_hash__ !== 'undefined') { - opts.release = __webpack_hash__; // eslint-disable-line no-undef + if (typeof webpackHash !== 'undefined') { + opts.release = webpackHash; } Raven.config(