Reduxify a bunch of the state, add seekTime URL parameter (#12)
* Start the process of reduxifying. Some of it works * better seeking * Seeking and stuff * Add segment functionality * Fix tests * Fix more testsmain
parent
dff700a462
commit
e755a4e911
|
@ -35,6 +35,7 @@
|
|||
"localforage": "^1.7.1",
|
||||
"moment": "^2.18.1",
|
||||
"node-sass": "^4.7.2",
|
||||
"obstruction": "^2.1.0",
|
||||
"prettier": "^1.9.2",
|
||||
"prop-types": "^15.5.10",
|
||||
"raven-js": "^3.16.0",
|
||||
|
@ -45,10 +46,13 @@
|
|||
"react-infinite": "^0.11.0",
|
||||
"react-list": "^0.8.6",
|
||||
"react-measure": "^2.0.2",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-scripts": "1.0.17",
|
||||
"react-test-renderer": "^16.2.0",
|
||||
"react-vega": "^3.0.0",
|
||||
"react-visibility-sensor": "^3.10.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"simple-statistics": "^4.1.0",
|
||||
"socket.io-client": "^2.0.3",
|
||||
"stream-selector": "^0.1.1",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect, Provider } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import Moment from "moment";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
|
@ -29,13 +31,15 @@ import UnloggerClient from "./api/unlogger";
|
|||
import * as ObjectUtils from "./utils/object";
|
||||
import { hash } from "./utils/string";
|
||||
|
||||
import { selectRoute, setLoading, loadRoutes } from "./actions";
|
||||
|
||||
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 CanOffsetFinder = require("./workers/can-offset-finder.worker.js");
|
||||
const CanStreamerWorker = require("./workers/CanStreamerWorker.worker.js");
|
||||
|
||||
export default class CanExplorer extends Component {
|
||||
class CanExplorer extends Component {
|
||||
static propTypes = {
|
||||
dongleId: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
|
@ -44,7 +48,8 @@ export default class CanExplorer extends Component {
|
|||
githubAuthToken: PropTypes.string,
|
||||
autoplay: PropTypes.bool,
|
||||
max: PropTypes.number,
|
||||
url: PropTypes.string
|
||||
url: PropTypes.string,
|
||||
selectedParts: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -52,13 +57,10 @@ export default class CanExplorer extends Component {
|
|||
this.state = {
|
||||
messages: {},
|
||||
selectedMessages: [],
|
||||
route: null,
|
||||
routes: [],
|
||||
canFrameOffset: -1,
|
||||
firstCanTime: 0,
|
||||
lastBusTime: null,
|
||||
selectedMessage: null,
|
||||
currentParts: [0, 0],
|
||||
showOnboarding: false,
|
||||
showLoadDbc: false,
|
||||
showSaveDbc: false,
|
||||
|
@ -68,7 +70,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 ? props.seekTime : 0,
|
||||
seekIndex: 0,
|
||||
maxByteStateChangeCount: 0,
|
||||
isLoading: true,
|
||||
|
@ -134,13 +136,8 @@ export default class CanExplorer extends Component {
|
|||
url: url,
|
||||
start_time: startTime
|
||||
};
|
||||
this.setState(
|
||||
{
|
||||
route,
|
||||
currentParts: [0, Math.min(max, PART_SEGMENT_LENGTH - 1)]
|
||||
},
|
||||
this.initCanData
|
||||
);
|
||||
this.props.dispatch(selectRoute(route));
|
||||
this.initCanData();
|
||||
} else if (auth.isAuthenticated() && !name) {
|
||||
Routes.fetchRoutes()
|
||||
.then(routes => {
|
||||
|
@ -148,7 +145,7 @@ export default class CanExplorer extends Component {
|
|||
Object.keys(routes).forEach(route => {
|
||||
_routes.push(routes[route]);
|
||||
});
|
||||
this.setState({ routes: _routes });
|
||||
this.props.dispatch(loadRoutes(_routes));
|
||||
if (!_routes[name]) {
|
||||
this.showOnboarding();
|
||||
}
|
||||
|
@ -162,14 +159,8 @@ export default class CanExplorer extends Component {
|
|||
if (routes && routes[name]) {
|
||||
// this makes fullname = dongleId + '|' + name
|
||||
const route = routes[name];
|
||||
const newState = {
|
||||
route,
|
||||
currentParts: [
|
||||
0,
|
||||
Math.min(route.proclog, PART_SEGMENT_LENGTH - 1)
|
||||
]
|
||||
};
|
||||
this.setState(newState, this.initCanData);
|
||||
this.props.dispatch(selectRoute(route));
|
||||
this.initCanData();
|
||||
} else {
|
||||
this.showOnboarding();
|
||||
}
|
||||
|
@ -180,10 +171,18 @@ export default class CanExplorer extends Component {
|
|||
} else {
|
||||
this.showOnboarding();
|
||||
}
|
||||
|
||||
this.onPartChange(this.props.selectedParts[0]);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (this.props.selectedParts[0] !== newProps.selectedParts[0]) {
|
||||
this.onPartChange(newProps.selectedParts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
initCanData() {
|
||||
const { route } = this.state;
|
||||
const { route } = this.props;
|
||||
|
||||
const offsetFinder = new CanOffsetFinder();
|
||||
offsetFinder.postMessage({
|
||||
|
@ -195,13 +194,13 @@ export default class CanExplorer extends Component {
|
|||
const { canFrameOffset, firstCanTime } = e.data;
|
||||
|
||||
this.setState({ canFrameOffset, firstCanTime }, () => {
|
||||
this.spawnWorker(this.state.currentParts);
|
||||
this.spawnWorker(this.props.selectedParts);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
onDbcSelected(dbcFilename, dbc) {
|
||||
const { route } = this.state;
|
||||
const { route } = this.props;
|
||||
this.hideLoadDbc();
|
||||
this.persistDbc({ dbcFilename, dbc });
|
||||
|
||||
|
@ -217,7 +216,7 @@ export default class CanExplorer extends Component {
|
|||
},
|
||||
() => {
|
||||
// Pass DBC text to webworker b/c can't pass instance of es6 class
|
||||
this.spawnWorker(this.state.currentParts);
|
||||
this.spawnWorker(this.props.selectedParts);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
@ -265,7 +264,8 @@ export default class CanExplorer extends Component {
|
|||
}
|
||||
downloadRawLogAsCSV(handler) {
|
||||
// Trigger file processing and dowload in worker
|
||||
const { firstCanTime, canFrameOffset, route } = this.state;
|
||||
const { firstCanTime, canFrameOffset } = this.state;
|
||||
const { route } = this.props;
|
||||
const worker = new LogCSVDownloader();
|
||||
|
||||
worker.onmessage = handler;
|
||||
|
@ -328,8 +328,8 @@ export default class CanExplorer extends Component {
|
|||
|
||||
spawnWorker(parts, options) {
|
||||
console.log("Spawning worker for", parts);
|
||||
if (!this.state.isLoading) {
|
||||
this.setState({ isLoading: true });
|
||||
if (!this.props.isLoading) {
|
||||
this.props.dispatch(setLoading(true));
|
||||
}
|
||||
// options is object of {part, prevMsgEntries, spawnWorkerHash, prepend}
|
||||
const [minPart, maxPart] = parts;
|
||||
|
@ -351,11 +351,11 @@ export default class CanExplorer extends Component {
|
|||
const {
|
||||
dbc,
|
||||
dbcFilename,
|
||||
route,
|
||||
firstCanTime,
|
||||
canFrameOffset,
|
||||
maxByteStateChangeCount
|
||||
} = this.state;
|
||||
const { route } = this.props;
|
||||
// var worker = new CanFetcher();
|
||||
var worker = new RLogDownloader();
|
||||
|
||||
|
@ -500,7 +500,7 @@ export default class CanExplorer extends Component {
|
|||
}
|
||||
|
||||
persistDbc({ dbcFilename, dbc }) {
|
||||
const { route } = this.state;
|
||||
const { route } = this.props;
|
||||
if (route) {
|
||||
persistDbc(route.fullname, { dbcFilename, dbc });
|
||||
} else {
|
||||
|
@ -529,21 +529,22 @@ export default class CanExplorer extends Component {
|
|||
}
|
||||
|
||||
partChangeDebounced = debounce(() => {
|
||||
const { currentParts } = this.state;
|
||||
this.spawnWorker(currentParts);
|
||||
const { selectedParts } = this.props;
|
||||
this.spawnWorker(selectedParts);
|
||||
}, 500);
|
||||
|
||||
onPartChange(part) {
|
||||
let { currentParts, canFrameOffset, route, messages } = this.state;
|
||||
let { canFrameOffset, messages } = this.state;
|
||||
let { route, selectedParts } = this.props;
|
||||
if (canFrameOffset === -1 || part + PART_SEGMENT_LENGTH > route.proclog) {
|
||||
return;
|
||||
}
|
||||
|
||||
// determine new parts to load, whether to prepend or append
|
||||
const currentPartSpan = currentParts[1] - currentParts[0] + 1;
|
||||
const currentPartSpan = selectedParts[1] - selectedParts[0] + 1;
|
||||
|
||||
// update current parts
|
||||
currentParts = [part, part + currentPartSpan - 1];
|
||||
selectedParts = [part, part + currentPartSpan - 1];
|
||||
|
||||
// update messages to only preserve entries in new part range
|
||||
const messagesKvPairs = Object.entries(messages).map(
|
||||
|
@ -558,10 +559,7 @@ export default class CanExplorer extends Component {
|
|||
messages = ObjectUtils.fromArray(messagesKvPairs);
|
||||
|
||||
// update state then load new parts
|
||||
this.setState(
|
||||
{ currentParts, messages, seekTime: part * 60 },
|
||||
this.partChangeDebounced
|
||||
);
|
||||
this.setState({ messages }, this.partChangeDebounced);
|
||||
}
|
||||
|
||||
showEditMessageModal(msgKey) {
|
||||
|
@ -615,11 +613,12 @@ export default class CanExplorer extends Component {
|
|||
seekIndex = 0;
|
||||
}
|
||||
|
||||
this.setState({ seekIndex, seekTime });
|
||||
this.setState({ seekIndex });
|
||||
}
|
||||
|
||||
onMessageSelected(msgKey) {
|
||||
let { seekTime, seekIndex, messages } = this.state;
|
||||
let { messages } = this.state;
|
||||
let { seekTime, seekIndex } = this.props;
|
||||
const msg = messages[msgKey];
|
||||
|
||||
if (seekTime > 0 && msg.entries.length > 0) {
|
||||
|
@ -643,7 +642,7 @@ export default class CanExplorer extends Component {
|
|||
}
|
||||
|
||||
loginWithGithub() {
|
||||
const { route } = this.state;
|
||||
const { route } = this.props;
|
||||
return (
|
||||
<a
|
||||
href={GithubAuth.authorizeUrl(
|
||||
|
@ -803,120 +802,129 @@ export default class CanExplorer extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
id="cabana"
|
||||
className={cx({ "is-showing-modal": this.showingModal() })}
|
||||
>
|
||||
{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">
|
||||
{this.state.isGithubAuthenticated ? (
|
||||
<div>
|
||||
<p>GitHub Authenticated</p>
|
||||
<p
|
||||
className="cabana-header-account-signout"
|
||||
onClick={this.githubSignOut}
|
||||
>
|
||||
Sign out
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
this.loginWithGithub()
|
||||
)}
|
||||
<Provider store={this.props.store}>
|
||||
<div
|
||||
id="cabana"
|
||||
className={cx({ "is-showing-modal": this.showingModal() })}
|
||||
>
|
||||
{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">
|
||||
{this.state.isGithubAuthenticated ? (
|
||||
<div>
|
||||
<p>GitHub Authenticated</p>
|
||||
<p
|
||||
className="cabana-header-account-signout"
|
||||
onClick={this.githubSignOut}
|
||||
>
|
||||
Sign out
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
this.loginWithGithub()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cabana-window">
|
||||
<Meta
|
||||
url={this.state.route ? this.state.route.url : null}
|
||||
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}
|
||||
seekIndex={this.state.seekIndex}
|
||||
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
||||
isDemo={this.props.isDemo}
|
||||
live={this.state.live}
|
||||
saveLog={debounce(this.downloadLogAsCSV, 500)}
|
||||
/>
|
||||
{this.state.route || this.state.live ? (
|
||||
<Explorer
|
||||
url={this.state.route ? this.state.route.url : null}
|
||||
live={this.state.live}
|
||||
<div className="cabana-window">
|
||||
<Meta
|
||||
url={this.props.route ? this.props.route.url : null}
|
||||
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}
|
||||
selectedMessages={this.state.selectedMessages}
|
||||
updateSelectedMessages={this.updateSelectedMessages}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
onPartChange={this.onPartChange}
|
||||
routeStartTime={
|
||||
this.state.route ? this.state.route.start_time : Moment()
|
||||
}
|
||||
partsCount={this.state.route ? this.state.route.proclog : 0}
|
||||
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.props.route}
|
||||
seekTime={this.props.seekTime}
|
||||
seekIndex={this.props.seekIndex}
|
||||
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
||||
isDemo={this.props.isDemo}
|
||||
live={this.state.live}
|
||||
saveLog={debounce(this.downloadLogAsCSV, 500)}
|
||||
/>
|
||||
{this.props.route || this.state.live ? (
|
||||
<Explorer
|
||||
url={this.props.route ? this.props.route.url : null}
|
||||
live={this.state.live}
|
||||
messages={this.state.messages}
|
||||
selectedMessage={this.state.selectedMessage}
|
||||
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
||||
canFrameOffset={this.state.canFrameOffset}
|
||||
firstCanTime={this.state.firstCanTime}
|
||||
seekTime={this.props.seekTime}
|
||||
seekIndex={this.props.seekIndex}
|
||||
partsLoaded={this.state.partsLoaded}
|
||||
autoplay={this.props.autoplay}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
onPartChange={this.onPartChange}
|
||||
routeStartTime={
|
||||
this.props.route ? this.props.route.start_time : Moment()
|
||||
}
|
||||
partsCount={this.props.route ? this.props.route.proclog : 0}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{this.state.showOnboarding ? (
|
||||
<OnboardingModal
|
||||
handlePandaConnect={this.handlePandaConnect}
|
||||
attemptingPandaConnection={this.state.attemptingPandaConnection}
|
||||
routes={this.props.routes}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{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}
|
||||
|
||||
{this.state.showEditMessageModal ? (
|
||||
<EditMessageModal
|
||||
handleClose={this.hideEditMessageModal}
|
||||
handleSave={this.onMessageFrameEdited}
|
||||
message={this.state.messages[this.state.editMessageModalMessage]}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{this.state.showOnboarding ? (
|
||||
<OnboardingModal
|
||||
handlePandaConnect={this.handlePandaConnect}
|
||||
attemptingPandaConnection={this.state.attemptingPandaConnection}
|
||||
routes={this.state.routes}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{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}
|
||||
|
||||
{this.state.showEditMessageModal ? (
|
||||
<EditMessageModal
|
||||
handleClose={this.hideEditMessageModal}
|
||||
handleSave={this.onMessageFrameEdited}
|
||||
message={this.state.messages[this.state.editMessageModalMessage]}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
seekTime: "playback.seekTime",
|
||||
seekIndex: "playback.seekIndex",
|
||||
selectedParts: "playback.selectedParts",
|
||||
isLoading: "playback.isLoading",
|
||||
route: "route.route",
|
||||
routes: "route.routes"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(CanExplorer);
|
||||
|
|
|
@ -4,6 +4,9 @@ import React from "react";
|
|||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("CanExplorer renders", () => {
|
||||
const canExplorer = shallow(<CanExplorer />);
|
||||
const canExplorer = shallow(<CanExplorer store={store} />);
|
||||
});
|
||||
|
|
|
@ -4,28 +4,34 @@ import CanGraph from "../../components/CanGraph";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("CanGraph successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<CanGraph
|
||||
onGraphRefAvailable={() => {}}
|
||||
unplot={() => {}}
|
||||
messages={{}}
|
||||
messageId={null}
|
||||
messageName={null}
|
||||
signalSpec={null}
|
||||
onSegmentChanged={() => {}}
|
||||
segment={[]}
|
||||
data={{}}
|
||||
onRelativeTimeClick={() => {}}
|
||||
currentTime={0}
|
||||
onDragStart={() => {}}
|
||||
onDragEnd={() => {}}
|
||||
container={null}
|
||||
dragPos={null}
|
||||
canReceiveGraphDrop={false}
|
||||
plottedSignals={[]}
|
||||
live={true}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<CanGraph
|
||||
onGraphRefAvailable={() => {}}
|
||||
unplot={() => {}}
|
||||
messages={{}}
|
||||
messageId={null}
|
||||
messageName={null}
|
||||
signalSpec={null}
|
||||
onSegmentChanged={() => {}}
|
||||
segment={[]}
|
||||
data={{}}
|
||||
onRelativeTimeClick={() => {}}
|
||||
currentTime={0}
|
||||
onDragStart={() => {}}
|
||||
onDragEnd={() => {}}
|
||||
container={null}
|
||||
dragPos={null}
|
||||
canReceiveGraphDrop={false}
|
||||
plottedSignals={[]}
|
||||
live={true}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,20 +4,26 @@ import CanGraphList from "../../components/CanGraphList";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("CanGraphList successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<CanGraphList
|
||||
plottedSignals={[]}
|
||||
messages={{}}
|
||||
graphData={[]}
|
||||
onGraphTimeClick={() => {}}
|
||||
seekTime={0}
|
||||
onSegmentChanged={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
segment={[]}
|
||||
mergePlots={() => {}}
|
||||
live={true}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<CanGraphList
|
||||
plottedSignals={[]}
|
||||
messages={{}}
|
||||
graphData={[]}
|
||||
onGraphTimeClick={() => {}}
|
||||
seekTime={0}
|
||||
onSegmentChanged={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
segment={[]}
|
||||
mergePlots={() => {}}
|
||||
live={true}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,18 +4,24 @@ import CanLog from "../../components/CanLog";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("CanLog successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<CanLog
|
||||
message={null}
|
||||
messageIndex={0}
|
||||
segmentIndices={[]}
|
||||
plottedSignals={[]}
|
||||
onSignalPlotPressed={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
showAddSignal={() => {}}
|
||||
onMessageExpanded={() => {}}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<CanLog
|
||||
message={null}
|
||||
messageIndex={0}
|
||||
segmentIndices={[]}
|
||||
plottedSignals={[]}
|
||||
onSignalPlotPressed={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
showAddSignal={() => {}}
|
||||
onMessageExpanded={() => {}}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -5,28 +5,34 @@ import React from "react";
|
|||
import Moment from "moment";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("Explorer successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<Explorer
|
||||
url={null}
|
||||
live={true}
|
||||
messages={{}}
|
||||
selectedMessage={null}
|
||||
onConfirmedSignalChange={() => {}}
|
||||
onSeek={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
canFrameOffset={0}
|
||||
firstCanTime={0}
|
||||
seekTime={0}
|
||||
seekIndex={0}
|
||||
currentParts={[0, 0]}
|
||||
partsLoaded={0}
|
||||
autoplay={true}
|
||||
showEditMessageModal={() => {}}
|
||||
onPartChange={() => {}}
|
||||
routeStartTime={Moment()}
|
||||
partsCount={0}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<Explorer
|
||||
url={null}
|
||||
live={true}
|
||||
messages={{}}
|
||||
selectedMessage={null}
|
||||
onConfirmedSignalChange={() => {}}
|
||||
onSeek={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
canFrameOffset={0}
|
||||
firstCanTime={0}
|
||||
seekTime={0}
|
||||
seekIndex={0}
|
||||
currentParts={[0, 0]}
|
||||
partsLoaded={0}
|
||||
autoplay={true}
|
||||
showEditMessageModal={() => {}}
|
||||
onPartChange={() => {}}
|
||||
routeStartTime={Moment()}
|
||||
partsCount={0}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -2,23 +2,29 @@ import HLS from "../../components/HLS";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("HLS successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<HLS
|
||||
source={"http://comma.ai"}
|
||||
startTime={0}
|
||||
playbackSpeed={1}
|
||||
onVideoElementAvailable={() => {}}
|
||||
playing={false}
|
||||
onClick={() => {}}
|
||||
onLoadStart={() => {}}
|
||||
onLoadEnd={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
segmentProgress={() => {}}
|
||||
shouldRestart={false}
|
||||
onRestart={() => {}}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<HLS
|
||||
source={"http://comma.ai"}
|
||||
startTime={0}
|
||||
playbackSpeed={1}
|
||||
onVideoElementAvailable={() => {}}
|
||||
playing={false}
|
||||
onClick={() => {}}
|
||||
onLoadStart={() => {}}
|
||||
onLoadEnd={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
segmentProgress={() => {}}
|
||||
shouldRestart={false}
|
||||
onRestart={() => {}}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -7,10 +7,16 @@ import MessageBytes from "../../components/MessageBytes";
|
|||
import DbcUtils from "../../utils/dbc";
|
||||
import DBC from "../../models/can/dbc";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("MessageBytes successfully mounts with minimal default props", () => {
|
||||
const message = DbcUtils.createMessageSpec(new DBC(), 0, "0", 1);
|
||||
const component = shallow(
|
||||
<MessageBytes seekTime={0} message={message} live={true} />
|
||||
<Provider store={store}>
|
||||
<MessageBytes seekTime={0} message={message} live={true} />
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,30 +4,36 @@ import Meta from "../../components/Meta";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("Meta successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<Meta
|
||||
url={null}
|
||||
messages={{}}
|
||||
selectedMessages={[]}
|
||||
updateSelectedMessages={() => {}}
|
||||
showEditMessageModal={() => {}}
|
||||
currentParts={[]}
|
||||
onMessageSelected={() => {}}
|
||||
onMessageUnselected={() => {}}
|
||||
showLoadDbc={() => {}}
|
||||
showSaveDbc={() => {}}
|
||||
dbcFilename={null}
|
||||
dbcLastSaved={null}
|
||||
dongleId={null}
|
||||
name={null}
|
||||
route={null}
|
||||
seekTime={0}
|
||||
seekIndex={0}
|
||||
maxByteStateChangeCount={0}
|
||||
isDemo={false}
|
||||
live={true}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<Meta
|
||||
url={null}
|
||||
messages={{}}
|
||||
selectedMessages={[]}
|
||||
updateSelectedMessages={() => {}}
|
||||
showEditMessageModal={() => {}}
|
||||
currentParts={[]}
|
||||
onMessageSelected={() => {}}
|
||||
onMessageUnselected={() => {}}
|
||||
showLoadDbc={() => {}}
|
||||
showSaveDbc={() => {}}
|
||||
dbcFilename={null}
|
||||
dbcLastSaved={null}
|
||||
dongleId={null}
|
||||
name={null}
|
||||
route={null}
|
||||
seekTime={0}
|
||||
seekIndex={0}
|
||||
maxByteStateChangeCount={0}
|
||||
isDemo={false}
|
||||
live={true}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,9 +4,15 @@ import PartSelector from "../../components/PartSelector";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("PartSelector successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<PartSelector onPartChange={() => {}} partsCount={0} />
|
||||
<Provider store={store}>
|
||||
<PartSelector onPartChange={() => {}} partsCount={0} />
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -4,21 +4,26 @@ import RouteSeeker from "../../components/RouteSeeker";
|
|||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
test("RouteSeeker successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<RouteSeeker
|
||||
nearestFrameTime={0}
|
||||
segmentProgress={() => {}}
|
||||
secondsLoaded={0}
|
||||
segmentIndices={[]}
|
||||
onUserSeek={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
videoElement={null}
|
||||
onPlay={() => {}}
|
||||
onPause={() => {}}
|
||||
playing={false}
|
||||
ratioTime={() => {}}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<RouteSeeker
|
||||
nearestFrameTime={0}
|
||||
segmentProgress={() => {}}
|
||||
secondsLoaded={0}
|
||||
onUserSeek={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
videoElement={null}
|
||||
onPlay={() => {}}
|
||||
onPause={() => {}}
|
||||
playing={false}
|
||||
ratioTime={() => {}}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -5,6 +5,10 @@ import React from "react";
|
|||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import createStore from "../../store";
|
||||
const store = createStore();
|
||||
|
||||
// Prevents style injection from firing after test finishes
|
||||
// and jsdom is torn down.
|
||||
beforeEach(() => {
|
||||
|
@ -16,23 +20,25 @@ afterEach(() => {
|
|||
|
||||
test("RouteVideoSync successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<RouteVideoSync
|
||||
message={null}
|
||||
secondsLoaded={0}
|
||||
startOffset={0}
|
||||
seekIndex={0}
|
||||
userSeekIndex={0}
|
||||
playing={false}
|
||||
url={"http://comma.ai"}
|
||||
canFrameOffset={0}
|
||||
firstCanTime={0}
|
||||
onVideoClick={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
onPlay={() => {}}
|
||||
onPause={() => {}}
|
||||
userSeekTime={0}
|
||||
/>
|
||||
<Provider store={store}>
|
||||
<RouteVideoSync
|
||||
message={null}
|
||||
secondsLoaded={0}
|
||||
startOffset={0}
|
||||
seekIndex={0}
|
||||
userSeekIndex={0}
|
||||
playing={false}
|
||||
url={"http://comma.ai"}
|
||||
canFrameOffset={0}
|
||||
firstCanTime={0}
|
||||
onVideoClick={() => {}}
|
||||
onPlaySeek={() => {}}
|
||||
onUserSeek={() => {}}
|
||||
onPlay={() => {}}
|
||||
onPause={() => {}}
|
||||
userSeekTime={0}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
import {
|
||||
ACTION_SEEK,
|
||||
ACTION_SELECT_PART,
|
||||
ACTION_AUTO_SEEK,
|
||||
ACTION_SET_LOADING,
|
||||
ACTION_SELECT_ROUTE,
|
||||
ACTION_LOAD_ROUTES,
|
||||
ACTION_SET_MAX_TIME,
|
||||
ACTION_SELECT_SEGMENT
|
||||
} from "./types";
|
||||
import { PART_SEGMENT_LENGTH } from "../config";
|
||||
|
||||
export function seek(time, index) {
|
||||
// console.log("Seek happened", time, index);
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
let suggestedPart = state.playback.selectedParts[0];
|
||||
if (
|
||||
time / 60 < state.playback.selectedParts[0] ||
|
||||
Math.floor(time / 60) > state.playback.selectedParts[1]
|
||||
) {
|
||||
suggestedPart = getPartForTime(time);
|
||||
}
|
||||
if (suggestedPart > state.route.maxParts - PART_SEGMENT_LENGTH + 1) {
|
||||
suggestedPart = state.route.maxParts - PART_SEGMENT_LENGTH + 1;
|
||||
}
|
||||
let maxPart = suggestedPart + PART_SEGMENT_LENGTH - 1;
|
||||
|
||||
if (maxPart > state.route.maxParts) {
|
||||
maxPart = state.route.maxParts;
|
||||
}
|
||||
if (suggestedPart !== state.playback.selectedParts[0]) {
|
||||
console.log(
|
||||
"changing part in a seek",
|
||||
state.playback.selectedParts[0],
|
||||
suggestedPart
|
||||
);
|
||||
}
|
||||
dispatch({
|
||||
type: ACTION_SEEK,
|
||||
selectedParts: [suggestedPart, maxPart],
|
||||
time,
|
||||
index
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function autoSeek(time) {
|
||||
// console.log("autoSeek happened", time);
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
let maxTime = Math.min(
|
||||
state.playback.maxTime,
|
||||
(1 + state.playback.selectedParts[1]) * 60
|
||||
);
|
||||
|
||||
if (state.segment.segment.length) {
|
||||
time = Math.max(state.segment.segment[0], time);
|
||||
maxTime = Math.min(maxTime, state.segment.segment[1]);
|
||||
}
|
||||
if (time >= maxTime) {
|
||||
time = state.playback.selectedParts[0] * 60;
|
||||
dispatch({
|
||||
type: ACTION_SEEK,
|
||||
selectedParts: state.playback.selectedParts,
|
||||
time
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: ACTION_AUTO_SEEK,
|
||||
time
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function selectPart(part) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
let selectedPart = part;
|
||||
if (selectedPart < 0) {
|
||||
selectedPart = state.playback.selectedParts[0];
|
||||
}
|
||||
if (selectedPart > state.route.maxParts - PART_SEGMENT_LENGTH + 1) {
|
||||
selectedPart = state.route.maxParts - PART_SEGMENT_LENGTH + 1;
|
||||
}
|
||||
let maxPart = selectedPart + PART_SEGMENT_LENGTH - 1;
|
||||
|
||||
if (maxPart > state.route.maxParts) {
|
||||
maxPart = state.route.maxParts;
|
||||
}
|
||||
if (selectedPart !== state.playback.selectedParts[0]) {
|
||||
console.log(
|
||||
"selecting new part!",
|
||||
state.playback.selectedParts[0],
|
||||
selectedPart
|
||||
);
|
||||
}
|
||||
|
||||
let seekTime = Math.min(
|
||||
getEndTimeForPart(selectedPart),
|
||||
state.playback.seekTime
|
||||
);
|
||||
seekTime = Math.max(getTimeForPart(selectedPart), seekTime);
|
||||
|
||||
dispatch({
|
||||
type: ACTION_SELECT_PART,
|
||||
selectedParts: [selectedPart, maxPart],
|
||||
part,
|
||||
seekTime
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setLoading(isLoading) {
|
||||
return {
|
||||
type: ACTION_SET_LOADING,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
|
||||
export function selectRoute(route) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: ACTION_SELECT_ROUTE,
|
||||
route
|
||||
});
|
||||
dispatch(selectPart(-1));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadRoutes(routes) {
|
||||
return {
|
||||
type: ACTION_LOAD_ROUTES,
|
||||
routes
|
||||
};
|
||||
}
|
||||
|
||||
export function setMaxTime(maxTime) {
|
||||
return {
|
||||
type: ACTION_SET_MAX_TIME,
|
||||
maxTime
|
||||
};
|
||||
}
|
||||
|
||||
export function selectSegment(segment, segmentIndices) {
|
||||
console.log("Sertting segment to", segment, segmentIndices);
|
||||
return {
|
||||
type: ACTION_SELECT_SEGMENT,
|
||||
segment,
|
||||
segmentIndices
|
||||
};
|
||||
}
|
||||
|
||||
function getPartForTime(time) {
|
||||
return Math.floor(time / 60);
|
||||
}
|
||||
|
||||
function getTimeForPart(part) {
|
||||
return Math.floor(part * 60);
|
||||
}
|
||||
|
||||
function getEndTimeForPart(part) {
|
||||
return Math.floor((part + PART_SEGMENT_LENGTH) * 60 - 1);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export const ACTION_SEEK = "ACTION_SEEK";
|
||||
export const ACTION_SELECT_PART = "ACTION_SELECT_PART";
|
||||
export const ACTION_AUTO_SEEK = "ACTION_AUTO_SEEK";
|
||||
export const ACTION_SET_LOADING = "ACTION_SET_LOADING";
|
||||
export const ACTION_LOAD_ROUTES = "ACTION_LOAD_ROUTES";
|
||||
export const ACTION_SELECT_ROUTE = "ACTION_SELECT_ROUTE";
|
||||
export const ACTION_SET_MAX_TIME = "ACTION_SET_MAX_TIME";
|
||||
export const ACTION_SELECT_SEGMENT = "ACTION_SELECT_SEGMENT";
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import Measure from "react-measure";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
|
@ -12,7 +14,7 @@ const DefaultPlotInnerStyle = {
|
|||
left: 0
|
||||
};
|
||||
|
||||
export default class CanGraph extends Component {
|
||||
class CanGraph extends Component {
|
||||
static emptyTable = [];
|
||||
|
||||
static propTypes = {
|
||||
|
@ -24,7 +26,7 @@ export default class CanGraph extends Component {
|
|||
segment: PropTypes.array,
|
||||
unplot: PropTypes.func,
|
||||
onRelativeTimeClick: PropTypes.func,
|
||||
currentTime: PropTypes.number,
|
||||
seekTime: PropTypes.number,
|
||||
onSegmentChanged: PropTypes.func,
|
||||
onDragStart: PropTypes.func,
|
||||
onDragEnd: PropTypes.func,
|
||||
|
@ -100,8 +102,8 @@ export default class CanGraph extends Component {
|
|||
segmentChanged = true;
|
||||
}
|
||||
|
||||
if (!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
|
||||
this.view.signal("videoTime", nextProps.currentTime);
|
||||
if (!nextProps.live && nextProps.seekTime !== this.props.seekTime) {
|
||||
this.view.signal("videoTime", nextProps.seekTime);
|
||||
segmentChanged = true;
|
||||
}
|
||||
|
||||
|
@ -308,3 +310,10 @@ export default class CanGraph extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
segment: "segment.segment",
|
||||
seekTime: "playback.seekTime"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(CanGraph);
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import CanGraph from "./CanGraph";
|
||||
|
||||
require("element-closest");
|
||||
|
||||
export default class CanGraphList extends Component {
|
||||
class CanGraphList extends Component {
|
||||
static propTypes = {
|
||||
plottedSignals: PropTypes.array.isRequired,
|
||||
messages: PropTypes.object.isRequired,
|
||||
graphData: PropTypes.array.isRequired,
|
||||
onGraphTimeClick: PropTypes.func.isRequired,
|
||||
seekTime: PropTypes.number.isRequired,
|
||||
onSegmentChanged: PropTypes.func.isRequired,
|
||||
onSignalUnplotPressed: PropTypes.func.isRequired,
|
||||
segment: PropTypes.array.isRequired,
|
||||
|
@ -152,10 +153,8 @@ export default class CanGraphList extends Component {
|
|||
messageName={msg.frame ? msg.frame.name : null}
|
||||
signalSpec={Object.assign(Object.create(signal), signal)}
|
||||
onSegmentChanged={this.props.onSegmentChanged}
|
||||
segment={this.props.segment}
|
||||
data={this.props.graphData[index]}
|
||||
onRelativeTimeClick={this.props.onGraphTimeClick}
|
||||
currentTime={this.props.seekTime}
|
||||
onDragStart={this.onGraphDragStart}
|
||||
onDragEnd={this.onGraphDragEnd}
|
||||
container={this.plotListRef}
|
||||
|
@ -185,3 +184,9 @@ export default class CanGraphList extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
segment: "segment.segment"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(CanGraphList);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactList from "react-list";
|
||||
|
||||
import cx from "classnames";
|
||||
|
||||
export default class CanLog extends Component {
|
||||
class CanLog extends Component {
|
||||
static ITEMS_PER_PAGE = 50;
|
||||
|
||||
static propTypes = {
|
||||
|
@ -308,3 +310,9 @@ export default class CanLog extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
segmentIndices: "segment.segmentIndices"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(CanLog);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import cx from "classnames";
|
||||
|
@ -13,7 +15,9 @@ import PartSelector from "./PartSelector";
|
|||
import PlaySpeedSelector from "./PlaySpeedSelector";
|
||||
import GraphData from "../models/graph-data";
|
||||
|
||||
export default class Explorer extends Component {
|
||||
import { seek, selectSegment } from "../actions";
|
||||
|
||||
class Explorer extends Component {
|
||||
static propTypes = {
|
||||
selectedMessage: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
|
@ -22,7 +26,6 @@ export default class Explorer extends Component {
|
|||
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
|
||||
|
@ -34,11 +37,9 @@ export default class Explorer extends Component {
|
|||
this.state = {
|
||||
plottedSignals: [],
|
||||
graphData: [],
|
||||
segment: [],
|
||||
segmentIndices: [],
|
||||
shouldShowAddSignal: true,
|
||||
userSeekIndex: 0,
|
||||
userSeekTime: props.currentParts[0] * 60,
|
||||
userSeekTime: this.props.seekTime,
|
||||
playing: props.autoplay,
|
||||
signals: {},
|
||||
playSpeed: 1
|
||||
|
@ -48,8 +49,6 @@ export default class Explorer extends Component {
|
|||
this.onSegmentChanged = this.onSegmentChanged.bind(this);
|
||||
this.showAddSignal = this.showAddSignal.bind(this);
|
||||
this.onGraphTimeClick = this.onGraphTimeClick.bind(this);
|
||||
this.onUserSeek = this.onUserSeek.bind(this);
|
||||
this.onPlaySeek = this.onPlaySeek.bind(this);
|
||||
this.onPlay = this.onPlay.bind(this);
|
||||
this.onPause = this.onPause.bind(this);
|
||||
this.onVideoClick = this.onVideoClick.bind(this);
|
||||
|
@ -109,6 +108,8 @@ export default class Explorer extends Component {
|
|||
const curMessage = this.props.messages[this.props.selectedMessage];
|
||||
let { plottedSignals, graphData } = this.state;
|
||||
|
||||
this.checkSeek(nextProps);
|
||||
|
||||
if (Object.keys(nextProps.messages).length === 0) {
|
||||
this.resetSegment();
|
||||
}
|
||||
|
@ -156,27 +157,12 @@ export default class Explorer extends Component {
|
|||
// corresponding to old message segment/seek times.
|
||||
|
||||
let { segment, segmentIndices } = this.clipSegment(
|
||||
this.state.segment,
|
||||
this.state.segmentIndices,
|
||||
this.props.segment,
|
||||
this.props.segmentIndices,
|
||||
nextMessage
|
||||
);
|
||||
|
||||
const nextSeekMsgEntry = nextMessage.entries[nextProps.seekIndex];
|
||||
let nextSeekTime;
|
||||
if (nextSeekMsgEntry) {
|
||||
nextSeekTime = nextSeekMsgEntry.relTime;
|
||||
} else if (segment.length === 2) {
|
||||
nextSeekTime = segment[0];
|
||||
} else {
|
||||
nextSeekTime = nextMessage.entries[0];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
segment,
|
||||
segmentIndices,
|
||||
userSeekIndex: nextProps.seekIndex,
|
||||
userSeekTime: nextSeekTime
|
||||
});
|
||||
this.props.dispatch(selectSegment(segment, segmentIndices));
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -185,16 +171,16 @@ export default class Explorer extends Component {
|
|||
nextMessage.entries.length !== curMessage.entries.length
|
||||
) {
|
||||
let { segment, segmentIndices } = this.clipSegment(
|
||||
this.state.segment,
|
||||
this.state.segmentIndices,
|
||||
this.props.segment,
|
||||
this.props.segmentIndices,
|
||||
nextMessage
|
||||
);
|
||||
this.setState({ segment, segmentIndices });
|
||||
this.props.dispatch(selectSegment(segment, segmentIndices));
|
||||
}
|
||||
|
||||
const partsDidChange =
|
||||
JSON.stringify(nextProps.currentParts) !==
|
||||
JSON.stringify(this.props.currentParts);
|
||||
JSON.stringify(nextProps.selectedParts) !==
|
||||
JSON.stringify(this.props.selectedParts);
|
||||
|
||||
if (plottedSignals.length > 0) {
|
||||
if (graphData.length !== plottedSignals.length || partsDidChange) {
|
||||
|
@ -233,8 +219,8 @@ export default class Explorer extends Component {
|
|||
const { userSeekTime } = this.state;
|
||||
const nextSeekTime =
|
||||
userSeekTime -
|
||||
this.props.currentParts[0] * 60 +
|
||||
nextProps.currentParts[0] * 60;
|
||||
this.props.selectedParts[0] * 60 +
|
||||
nextProps.selectedParts[0] * 60;
|
||||
this.setState({ userSeekTime: nextSeekTime });
|
||||
}
|
||||
}
|
||||
|
@ -246,11 +232,14 @@ export default class Explorer extends Component {
|
|||
}
|
||||
|
||||
timeWindow() {
|
||||
const { routeStartTime, currentParts } = this.props;
|
||||
const { routeStartTime, selectedParts } = this.props;
|
||||
|
||||
if (routeStartTime) {
|
||||
const partStartOffset = currentParts[0] * 60,
|
||||
partEndOffset = (currentParts[1] + 1) * 60;
|
||||
const partStartOffset = selectedParts[0] * 60;
|
||||
const partEndOffset = Math.min(
|
||||
this.props.maxTime,
|
||||
(selectedParts[1] + 1) * 60
|
||||
);
|
||||
|
||||
const windowStartTime = routeStartTime
|
||||
.clone()
|
||||
|
@ -339,11 +328,10 @@ export default class Explorer extends Component {
|
|||
const segmentIndices = Entries.findSegmentIndices(entries, segment, true);
|
||||
|
||||
this.setState({
|
||||
segment,
|
||||
segmentIndices,
|
||||
userSeekIndex: segmentIndices[0],
|
||||
userSeekTime: segment[0]
|
||||
});
|
||||
this.props.dispatch(selectSegment(segment, segmentIndices));
|
||||
}, 250);
|
||||
|
||||
onSegmentChanged(messageId, segment) {
|
||||
|
@ -353,8 +341,7 @@ export default class Explorer extends Component {
|
|||
}
|
||||
|
||||
resetSegment() {
|
||||
const { segment, segmentIndices } = this.state;
|
||||
const { messages, selectedMessage } = this.props;
|
||||
const { segment, segmentIndices, messages, selectedMessage } = this.props;
|
||||
if (segment.length > 0 || segmentIndices.length > 0) {
|
||||
let userSeekTime = 0;
|
||||
if (
|
||||
|
@ -364,11 +351,10 @@ export default class Explorer extends Component {
|
|||
userSeekTime = messages[selectedMessage].entries[0].relTime;
|
||||
}
|
||||
this.setState({
|
||||
segment: [],
|
||||
segmentIndices: [],
|
||||
userSeekIndex: 0,
|
||||
userSeekTime
|
||||
});
|
||||
this.props.dispatch(selectSegment([], []));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,13 +366,18 @@ export default class Explorer extends Component {
|
|||
this.setState({ shouldShowAddSignal: !this.state.shouldShowAddSignal });
|
||||
}
|
||||
|
||||
indexFromSeekTime(time) {
|
||||
indexFromSeekTime(time, entries) {
|
||||
// returns index guaranteed to be in [0, entries.length - 1]
|
||||
|
||||
const { entries } = this.props.messages[this.props.selectedMessage];
|
||||
if (entries.length === 0) return null;
|
||||
if (!entries) {
|
||||
entries = this.props.messages[this.props.selectedMessage].entries;
|
||||
}
|
||||
|
||||
const { segmentIndices } = this.state;
|
||||
if (entries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { segmentIndices } = this.props;
|
||||
if (segmentIndices.length === 2) {
|
||||
for (let i = segmentIndices[0]; i <= segmentIndices[1]; i++) {
|
||||
if (entries[i].relTime >= time) {
|
||||
|
@ -404,32 +395,33 @@ export default class Explorer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onUserSeek(time) {
|
||||
this.setState({ userSeekTime: time });
|
||||
const message = this.props.messages[this.props.selectedMessage];
|
||||
checkSeek(newProps) {
|
||||
const message = newProps.messages[newProps.selectedMessage];
|
||||
if (!message) {
|
||||
this.props.onUserSeek(time);
|
||||
this.props.onSeek(0, time);
|
||||
// remove seekIndex
|
||||
if (newProps.seekIndex) {
|
||||
this.props.dispatch(seek(newProps.seekTime));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { entries } = message;
|
||||
const userSeekIndex = this.indexFromSeekTime(time);
|
||||
const userSeekIndex = this.indexFromSeekTime(newProps.seekTime, entries);
|
||||
if (userSeekIndex) {
|
||||
const seekTime = entries[userSeekIndex].relTime;
|
||||
|
||||
this.setState({ userSeekIndex, userSeekTime: seekTime });
|
||||
this.props.onSeek(userSeekIndex, seekTime);
|
||||
} else {
|
||||
this.props.onUserSeek(time);
|
||||
this.setState({ userSeekTime: time });
|
||||
this.setState({ indexSeekTime: seekTime });
|
||||
if (userSeekIndex !== newProps.seekIndex) {
|
||||
this.props.dispatch(seek(newProps.seekTime, userSeekIndex));
|
||||
}
|
||||
} else if (newProps.seekIndex) {
|
||||
this.props.dispatch(seek(newProps.seekTime));
|
||||
}
|
||||
}
|
||||
|
||||
onPlaySeek(time) {
|
||||
const message = this.props.messages[this.props.selectedMessage];
|
||||
if (!message || message.entries.length === 0) {
|
||||
this.props.onSeek(0, time);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -464,19 +456,19 @@ export default class Explorer extends Component {
|
|||
this.setState({ playing: false });
|
||||
}
|
||||
|
||||
secondsLoadedRouteRelative(currentParts) {
|
||||
return (currentParts[1] - currentParts[0] + 1) * 60;
|
||||
secondsLoadedRouteRelative(selectedParts) {
|
||||
return (selectedParts[1] - selectedParts[0] + 1) * 60;
|
||||
}
|
||||
|
||||
secondsLoaded() {
|
||||
const message = this.props.messages[this.props.selectedMessage];
|
||||
if (!message || message.entries.length === 0) {
|
||||
return this.secondsLoadedRouteRelative(this.props.currentParts);
|
||||
return this.secondsLoadedRouteRelative(this.props.selectedParts);
|
||||
}
|
||||
|
||||
const { entries } = message;
|
||||
|
||||
const { segment } = this.state;
|
||||
const { segment } = this.props;
|
||||
if (segment.length === 2) {
|
||||
return segment[1] - segment[0];
|
||||
} else {
|
||||
|
@ -485,14 +477,14 @@ export default class Explorer extends Component {
|
|||
}
|
||||
|
||||
startOffset() {
|
||||
const partOffset = this.props.currentParts[0] * 60;
|
||||
const partOffset = this.props.selectedParts[0] * 60;
|
||||
const message = this.props.messages[this.props.selectedMessage];
|
||||
if (!message || message.entries.length === 0) {
|
||||
return partOffset;
|
||||
}
|
||||
|
||||
const { entries } = message;
|
||||
const { segment } = this.state;
|
||||
const { segment } = this.props;
|
||||
let startTime;
|
||||
if (segment.length === 2) {
|
||||
startTime = segment[0];
|
||||
|
@ -502,7 +494,7 @@ export default class Explorer extends Component {
|
|||
|
||||
if (
|
||||
startTime > partOffset &&
|
||||
startTime < (this.props.currentParts[1] + 1) * 60
|
||||
startTime < (this.props.selectedParts[1] + 1) * 60
|
||||
) {
|
||||
// startTime is within bounds of currently selected parts
|
||||
return startTime;
|
||||
|
@ -661,10 +653,7 @@ export default class Explorer extends Component {
|
|||
/>
|
||||
<div className="cabana-explorer-visuals-header">
|
||||
{this.timeWindow()}
|
||||
<PartSelector
|
||||
onPartChange={this.props.onPartChange}
|
||||
partsCount={this.props.partsCount}
|
||||
/>
|
||||
<PartSelector partsCount={this.props.partsCount} />
|
||||
</div>
|
||||
<RouteVideoSync
|
||||
message={this.props.messages[this.props.selectedMessage]}
|
||||
|
@ -677,7 +666,6 @@ export default class Explorer extends Component {
|
|||
canFrameOffset={this.props.canFrameOffset}
|
||||
firstCanTime={this.props.firstCanTime}
|
||||
onVideoClick={this.onVideoClick}
|
||||
onPlaySeek={this.onPlaySeek}
|
||||
onUserSeek={this.onUserSeek}
|
||||
onPlay={this.onPlay}
|
||||
onPause={this.onPause}
|
||||
|
@ -686,7 +674,7 @@ export default class Explorer extends Component {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.segment.length > 0 ? (
|
||||
{this.props.segment.length > 0 ? (
|
||||
<div
|
||||
className={"cabana-explorer-visuals-segmentreset"}
|
||||
onClick={() => {
|
||||
|
@ -701,10 +689,8 @@ export default class Explorer extends Component {
|
|||
messages={this.props.messages}
|
||||
graphData={this.state.graphData}
|
||||
onGraphTimeClick={this.onGraphTimeClick}
|
||||
seekTime={this.props.seekTime}
|
||||
onSegmentChanged={this.onSegmentChanged}
|
||||
onSignalUnplotPressed={this.onSignalUnplotPressed}
|
||||
segment={this.state.segment}
|
||||
mergePlots={this.mergePlots}
|
||||
live={this.props.live}
|
||||
/>
|
||||
|
@ -713,3 +699,14 @@ export default class Explorer extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
selectedParts: "playback.selectedParts",
|
||||
seekTime: "playback.seekTime",
|
||||
seekIndex: "playback.seekIndex",
|
||||
maxTime: "playback.maxTime",
|
||||
segment: "segment.segment",
|
||||
segmentIndices: "segment.segmentIndices"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(Explorer);
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
import Hls from "hls.js/lib";
|
||||
|
||||
export default class HLS extends Component {
|
||||
import { setLoading, seek, autoSeek, setMaxTime } from "../actions";
|
||||
|
||||
class HLS extends Component {
|
||||
static propTypes = {
|
||||
source: PropTypes.string.isRequired,
|
||||
startTime: PropTypes.number.isRequired,
|
||||
|
@ -10,14 +14,24 @@ export default class HLS extends Component {
|
|||
playing: PropTypes.bool.isRequired,
|
||||
onVideoElementAvailable: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onLoadStart: PropTypes.func,
|
||||
onLoadEnd: PropTypes.func,
|
||||
onPlaySeek: PropTypes.func,
|
||||
segmentProgress: PropTypes.func,
|
||||
shouldRestart: PropTypes.bool,
|
||||
onRestart: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
seekingTime: null
|
||||
};
|
||||
|
||||
this.onLoadStart = this.onLoadStart.bind(this);
|
||||
this.onLoadEnd = this.onLoadEnd.bind(this);
|
||||
this.onEnded = this.onEnded.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
(nextProps.shouldRestart ||
|
||||
|
@ -46,12 +60,23 @@ export default class HLS extends Component {
|
|||
} else {
|
||||
this.videoElement.pause();
|
||||
}
|
||||
|
||||
if (
|
||||
this.videoElement &&
|
||||
Math.abs(this.videoElement.currentTime - nextProps.seekTime) > 1.0
|
||||
) {
|
||||
this.videoElement.currentTime = nextProps.seekTime;
|
||||
this.setState({
|
||||
seekingTime: nextProps.seekTime
|
||||
});
|
||||
this.props.dispatch(setLoading(true));
|
||||
}
|
||||
}
|
||||
|
||||
onSeeking = () => {
|
||||
if (!this.props.playing) {
|
||||
this.props.onLoadStart();
|
||||
this.props.onPlaySeek(this.videoElement.currentTime);
|
||||
this.onLoadStart();
|
||||
this.props.dispatch(seek(this.videoElement.currentTime));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -61,10 +86,10 @@ export default class HLS extends Component {
|
|||
onSeeked = () => {
|
||||
if (!this.props.playing) {
|
||||
if (this.shouldInitVideoTime) {
|
||||
this.videoElement.currentTime = this.props.startTime;
|
||||
// this.videoElement.currentTime = this.props.startTime;
|
||||
this.shouldInitVideoTime = false;
|
||||
}
|
||||
this.props.onLoadEnd();
|
||||
this.onLoadEnd();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -85,9 +110,33 @@ export default class HLS extends Component {
|
|||
// destroy hls video source
|
||||
if (this.player) {
|
||||
this.player.destroy();
|
||||
this.player = null;
|
||||
}
|
||||
}
|
||||
|
||||
onLoadStart() {
|
||||
if (!this.state.seekingTime) {
|
||||
this.videoElement.currentTime = this.props.seekTime;
|
||||
this.setState({
|
||||
seekingTime: this.props.seekTime
|
||||
});
|
||||
}
|
||||
this.props.dispatch(setLoading(true));
|
||||
}
|
||||
onLoadEnd() {
|
||||
if (this.videoElement.duration !== this.props.maxTime) {
|
||||
this.props.dispatch(setMaxTime(this.videoElement.duration));
|
||||
}
|
||||
this.props.dispatch(setLoading(false));
|
||||
this.setState({
|
||||
seekingTime: null
|
||||
});
|
||||
}
|
||||
|
||||
onEnded() {
|
||||
this.props.dispatch(autoSeek(this.props.maxTime));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
|
@ -98,14 +147,24 @@ export default class HLS extends Component {
|
|||
ref={video => {
|
||||
this.videoElement = video;
|
||||
}}
|
||||
seek={this.props.seekTime}
|
||||
autoPlay={this.props.playing}
|
||||
muted
|
||||
onWaiting={this.props.onLoadStart}
|
||||
onPlaying={this.props.onLoadEnd}
|
||||
onWaiting={this.onLoadStart}
|
||||
onPlaying={this.onLoadEnd}
|
||||
onSeeking={this.onSeeking}
|
||||
onSeeked={this.onSeeked}
|
||||
onEnded={this.onEnded}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
isLoading: "playback.isLoading",
|
||||
seekTime: "playback.seekTime",
|
||||
maxTime: "playback.maxTime"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(HLS);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default class MessageBytes extends Component {
|
||||
class MessageBytes extends Component {
|
||||
static propTypes = {
|
||||
seekTime: PropTypes.number.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
|
@ -14,7 +16,7 @@ export default class MessageBytes extends Component {
|
|||
this.state = {
|
||||
isVisible: true,
|
||||
lastMessageIndex: 0,
|
||||
lastSeekTime: 0
|
||||
lastSeekTime: props.seekTime
|
||||
};
|
||||
|
||||
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
||||
|
@ -136,3 +138,10 @@ export default class MessageBytes extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
seekTime: "playback.seekTime",
|
||||
seekItem: "playback.seekItem"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(MessageBytes);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import cx from "classnames";
|
||||
import PropTypes from "prop-types";
|
||||
import Clipboard from "clipboard";
|
||||
|
@ -8,7 +10,7 @@ import MessageBytes from "./MessageBytes";
|
|||
import { GITHUB_AUTH_TOKEN_KEY } from "../config";
|
||||
const { ckmeans } = require("simple-statistics");
|
||||
|
||||
export default class Meta extends Component {
|
||||
class Meta extends Component {
|
||||
static propTypes = {
|
||||
onMessageSelected: PropTypes.func,
|
||||
onMessageUnselected: PropTypes.func,
|
||||
|
@ -25,7 +27,7 @@ export default class Meta extends Component {
|
|||
showEditMessageModal: PropTypes.func,
|
||||
route: PropTypes.object,
|
||||
partsLoaded: PropTypes.number,
|
||||
currentParts: PropTypes.array,
|
||||
selectedParts: PropTypes.array,
|
||||
seekTime: PropTypes.number,
|
||||
loginWithGithub: PropTypes.element,
|
||||
isDemo: PropTypes.bool,
|
||||
|
@ -275,13 +277,7 @@ export default class Meta extends Component {
|
|||
<td>{msg.entries.length}</td>
|
||||
<td>
|
||||
<div className="cabana-meta-messages-list-item-bytes">
|
||||
<MessageBytes
|
||||
key={msg.id}
|
||||
message={msg}
|
||||
seekIndex={this.props.seekIndex}
|
||||
seekTime={this.props.seekTime}
|
||||
live={this.props.live}
|
||||
/>
|
||||
<MessageBytes key={msg.id} message={msg} live={this.props.live} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -447,3 +443,10 @@ export default class Meta extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
route: "playback.route",
|
||||
seekTime: "playback.seekTime"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(Meta);
|
||||
|
|
|
@ -1,24 +1,33 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { selectPart } from "../actions";
|
||||
|
||||
import { PART_SEGMENT_LENGTH } from "../config";
|
||||
|
||||
export default class PartSelector extends Component {
|
||||
class PartSelector extends Component {
|
||||
static selectorWidth = 150;
|
||||
static propTypes = {
|
||||
onPartChange: PropTypes.func.isRequired,
|
||||
partsCount: PropTypes.number.isRequired
|
||||
maxParts: PropTypes.number.isRequired,
|
||||
selectedParts: PropTypes.array,
|
||||
seekTime: PropTypes.number
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
|
||||
selectedPart: 0,
|
||||
selectedPartStyle: this.makePartStyle(
|
||||
props.maxParts,
|
||||
props.selectedParts[0]
|
||||
),
|
||||
isDragging: false
|
||||
};
|
||||
|
||||
console.log("Constructing");
|
||||
|
||||
this.selectNextPart = this.selectNextPart.bind(this);
|
||||
this.selectPrevPart = this.selectPrevPart.bind(this);
|
||||
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
|
||||
|
@ -27,18 +36,23 @@ export default class PartSelector extends Component {
|
|||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
makePartStyle(partsCount, selectedPart) {
|
||||
makePartStyle(maxParts, selectedPart) {
|
||||
maxParts = maxParts + 1;
|
||||
console.log("Making styles for", maxParts, selectedPart);
|
||||
return {
|
||||
left: selectedPart / partsCount * PartSelector.selectorWidth,
|
||||
width: PART_SEGMENT_LENGTH / partsCount * PartSelector.selectorWidth
|
||||
left: selectedPart / maxParts * PartSelector.selectorWidth,
|
||||
width: (PART_SEGMENT_LENGTH - 1) / maxParts * PartSelector.selectorWidth
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.partsCount !== this.props.partsCount) {
|
||||
if (
|
||||
nextProps.maxParts !== this.props.maxParts ||
|
||||
nextProps.selectedParts[0] !== this.props.selectedParts[0]
|
||||
) {
|
||||
const selectedPartStyle = this.makePartStyle(
|
||||
nextProps.partsCount,
|
||||
this.state.selectedPart
|
||||
nextProps.maxParts,
|
||||
nextProps.selectedParts[0]
|
||||
);
|
||||
this.setState({ selectedPartStyle });
|
||||
}
|
||||
|
@ -47,23 +61,19 @@ export default class PartSelector extends Component {
|
|||
selectPart(part) {
|
||||
part = Math.max(
|
||||
0,
|
||||
Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part)
|
||||
Math.min(this.props.maxParts - PART_SEGMENT_LENGTH + 1, part)
|
||||
);
|
||||
if (part === this.state.selectedPart) {
|
||||
if (part === this.props.selectedParts[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onPartChange(part);
|
||||
this.setState({
|
||||
selectedPart: part,
|
||||
selectedPartStyle: this.makePartStyle(this.props.partsCount, part)
|
||||
});
|
||||
this.props.dispatch(selectPart(part));
|
||||
}
|
||||
|
||||
selectNextPart() {
|
||||
let { selectedPart } = this.state;
|
||||
selectedPart++;
|
||||
if (selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
|
||||
let { selectedParts, maxParts } = this.props;
|
||||
let selectedPart = selectedParts[0] + 1;
|
||||
if (selectedPart + PART_SEGMENT_LENGTH > maxParts) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,8 +81,8 @@ export default class PartSelector extends Component {
|
|||
}
|
||||
|
||||
selectPrevPart() {
|
||||
let { selectedPart } = this.state;
|
||||
selectedPart--;
|
||||
let { selectedParts } = this.props;
|
||||
let selectedPart = selectedParts[0] - 1;
|
||||
if (selectedPart < 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -83,7 +93,7 @@ export default class PartSelector extends Component {
|
|||
partAtClientX(clientX) {
|
||||
const rect = this.selectorRect.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
|
||||
return Math.floor(x * this.props.maxParts / PartSelector.selectorWidth);
|
||||
}
|
||||
|
||||
onSelectedPartDragStart(e) {
|
||||
|
@ -110,9 +120,8 @@ export default class PartSelector extends Component {
|
|||
|
||||
render() {
|
||||
const { selectedPartStyle } = this.state;
|
||||
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
|
||||
if (this.props.maxParts <= PART_SEGMENT_LENGTH) {
|
||||
// all parts are available so no need to render the partselector
|
||||
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
@ -135,3 +144,11 @@ export default class PartSelector extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
seekTime: "playback.seekTime",
|
||||
selectedParts: "playback.selectedParts",
|
||||
maxParts: "route.maxParts"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(PartSelector);
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
import PlayButton from "../PlayButton";
|
||||
import debounce from "../../utils/debounce";
|
||||
|
||||
export default class RouteSeeker extends Component {
|
||||
import { autoSeek, seek } from "../../actions";
|
||||
|
||||
class RouteSeeker extends Component {
|
||||
static propTypes = {
|
||||
secondsLoaded: PropTypes.number.isRequired,
|
||||
segmentIndices: PropTypes.arrayOf(PropTypes.number),
|
||||
onUserSeek: PropTypes.func,
|
||||
onPlaySeek: PropTypes.func,
|
||||
video: PropTypes.node,
|
||||
onPause: PropTypes.func,
|
||||
onPlay: PropTypes.func,
|
||||
|
@ -88,7 +90,7 @@ export default class RouteSeeker extends Component {
|
|||
return 100 * (x / this.progressBar.offsetWidth);
|
||||
}
|
||||
|
||||
updateDraggingSeek = debounce(ratio => this.props.onUserSeek(ratio), 250);
|
||||
updateDraggingSeek = debounce(ratio => this.props.dispatch(seek(ratio)), 250);
|
||||
|
||||
onMouseMove(e) {
|
||||
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
|
||||
|
@ -111,7 +113,7 @@ export default class RouteSeeker extends Component {
|
|||
const ratio = Math.max(0, markerOffsetPct / 100);
|
||||
if (this.state.isDragging) {
|
||||
this.updateSeekedBar(ratio);
|
||||
this.updateDraggingSeek(ratio);
|
||||
// this.updateDraggingSeek(ratio);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -138,7 +140,16 @@ export default class RouteSeeker extends Component {
|
|||
let ratio = this.mouseEventXOffsetPercent(e) / 100;
|
||||
ratio = Math.min(1, Math.max(0, ratio));
|
||||
this.updateSeekedBar(ratio);
|
||||
this.props.onUserSeek(ratio);
|
||||
this.seek(this.props.ratioTime(ratio));
|
||||
}
|
||||
|
||||
seek(time) {
|
||||
this.isSeeking = true;
|
||||
this.props.dispatch(seek(time));
|
||||
const { videoElement } = this.props;
|
||||
if (videoElement) {
|
||||
videoElement.currentTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
onPlay() {
|
||||
|
@ -153,12 +164,17 @@ export default class RouteSeeker extends Component {
|
|||
|
||||
executePlayTimer() {
|
||||
const { videoElement } = this.props;
|
||||
if (videoElement === null) {
|
||||
if (this.isSeeking || !videoElement) {
|
||||
this.isSeeking = false;
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
const { currentTime } = videoElement;
|
||||
if (!currentTime) {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
let newRatio = this.props.segmentProgress(currentTime);
|
||||
|
||||
if (newRatio === this.state.ratio) {
|
||||
|
@ -168,12 +184,13 @@ export default class RouteSeeker extends Component {
|
|||
|
||||
if (newRatio >= 1) {
|
||||
newRatio = 0;
|
||||
this.props.onUserSeek(newRatio);
|
||||
// whats this?
|
||||
// this.props.dispatch(seek(newRatio));
|
||||
}
|
||||
|
||||
if (newRatio >= 0) {
|
||||
this.updateSeekedBar(newRatio);
|
||||
this.props.onPlaySeek(currentTime);
|
||||
this.props.dispatch(autoSeek(currentTime));
|
||||
}
|
||||
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
|
@ -235,3 +252,9 @@ export default class RouteSeeker extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
segmentIndices: "segment.segmentIndices"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(RouteSeeker);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Obstruction from "obstruction";
|
||||
import PropTypes from "prop-types";
|
||||
import { StyleSheet, css } from "aphrodite/no-important";
|
||||
|
||||
|
@ -7,6 +9,8 @@ import { cameraPath } from "../api/routes";
|
|||
import Video from "../api/video";
|
||||
import RouteSeeker from "./RouteSeeker/RouteSeeker";
|
||||
|
||||
import { seek } from "../actions";
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
loadingOverlay: {
|
||||
position: "absolute",
|
||||
|
@ -44,7 +48,7 @@ const Styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
export default class RouteVideoSync extends Component {
|
||||
class RouteVideoSync extends Component {
|
||||
static propTypes = {
|
||||
userSeekIndex: PropTypes.number.isRequired,
|
||||
secondsLoaded: PropTypes.number.isRequired,
|
||||
|
@ -54,8 +58,6 @@ export default class RouteVideoSync extends Component {
|
|||
canFrameOffset: PropTypes.number.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
playing: PropTypes.bool.isRequired,
|
||||
onPlaySeek: PropTypes.func.isRequired,
|
||||
onUserSeek: PropTypes.func.isRequired,
|
||||
onPlay: PropTypes.func.isRequired,
|
||||
onPause: PropTypes.func.isRequired,
|
||||
userSeekTime: PropTypes.number.isRequired
|
||||
|
@ -64,14 +66,10 @@ export default class RouteVideoSync extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
shouldShowJpeg: true,
|
||||
isLoading: true,
|
||||
videoElement: null,
|
||||
shouldRestartHls: false
|
||||
};
|
||||
|
||||
this.onLoadStart = this.onLoadStart.bind(this);
|
||||
this.onLoadEnd = this.onLoadEnd.bind(this);
|
||||
this.segmentProgress = this.segmentProgress.bind(this);
|
||||
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
|
||||
this.onUserSeek = this.onUserSeek.bind(this);
|
||||
|
@ -93,7 +91,7 @@ export default class RouteVideoSync extends Component {
|
|||
|
||||
nearestFrameUrl() {
|
||||
const { url } = this.props;
|
||||
const sec = Math.round(this.props.userSeekTime);
|
||||
const sec = Math.round(this.props.seekTime);
|
||||
return cameraPath(url, sec);
|
||||
}
|
||||
|
||||
|
@ -109,34 +107,30 @@ export default class RouteVideoSync extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
onLoadStart() {
|
||||
this.setState({
|
||||
shouldShowJpeg: true,
|
||||
isLoading: true
|
||||
});
|
||||
}
|
||||
|
||||
onLoadEnd() {
|
||||
this.setState({
|
||||
shouldShowJpeg: false,
|
||||
isLoading: false
|
||||
});
|
||||
}
|
||||
|
||||
segmentProgress(currentTime) {
|
||||
// returns progress as number in [0,1]
|
||||
|
||||
if (currentTime < this.props.startOffset) {
|
||||
currentTime = this.props.startOffset;
|
||||
}
|
||||
|
||||
const ratio =
|
||||
(currentTime - this.props.startOffset) / this.props.secondsLoaded;
|
||||
let partMaxTime = Math.min(
|
||||
this.props.maxTime,
|
||||
(1 + this.props.selectedParts[1]) * 60
|
||||
);
|
||||
let partDuration = partMaxTime - this.props.startOffset;
|
||||
|
||||
const ratio = (currentTime - this.props.startOffset) / partDuration;
|
||||
return Math.max(0, Math.min(1, ratio));
|
||||
}
|
||||
|
||||
ratioTime(ratio) {
|
||||
return ratio * this.props.secondsLoaded + this.props.startOffset;
|
||||
let partMaxTime = Math.min(
|
||||
this.props.maxTime,
|
||||
(1 + this.props.selectedParts[1]) * 60
|
||||
);
|
||||
let partDuration = partMaxTime - this.props.startOffset;
|
||||
|
||||
return ratio * partDuration + this.props.startOffset;
|
||||
}
|
||||
|
||||
onVideoElementAvailable(videoElement) {
|
||||
|
@ -146,7 +140,8 @@ export default class RouteVideoSync extends Component {
|
|||
onUserSeek(ratio) {
|
||||
/* ratio in [0,1] */
|
||||
|
||||
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
|
||||
const funcSeekToRatio = () =>
|
||||
this.props.dispatch(seek(this.ratioTime(ratio)));
|
||||
if (ratio === 0) {
|
||||
this.setState({ shouldRestartHls: true }, funcSeekToRatio);
|
||||
} else {
|
||||
|
@ -161,8 +156,8 @@ export default class RouteVideoSync extends Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="cabana-explorer-visuals-camera">
|
||||
{this.state.isLoading ? this.loadingOverlay() : null}
|
||||
{this.state.shouldShowJpeg ? (
|
||||
{this.props.isLoading ? this.loadingOverlay() : null}
|
||||
{this.props.isLoading ? (
|
||||
<img
|
||||
src={this.nearestFrameUrl()}
|
||||
className={css(Styles.img)}
|
||||
|
@ -177,10 +172,6 @@ export default class RouteVideoSync extends Component {
|
|||
onVideoElementAvailable={this.onVideoElementAvailable}
|
||||
playing={this.props.playing}
|
||||
onClick={this.props.onVideoClick}
|
||||
onLoadStart={this.onLoadStart}
|
||||
onLoadEnd={this.onLoadEnd}
|
||||
onUserSeek={this.onUserSeek}
|
||||
onPlaySeek={this.props.onPlaySeek}
|
||||
segmentProgress={this.segmentProgress}
|
||||
shouldRestart={this.state.shouldRestartHls}
|
||||
onRestart={this.onHlsRestart}
|
||||
|
@ -190,9 +181,6 @@ export default class RouteVideoSync extends Component {
|
|||
nearestFrameTime={this.props.userSeekTime}
|
||||
segmentProgress={this.segmentProgress}
|
||||
secondsLoaded={this.props.secondsLoaded}
|
||||
segmentIndices={this.props.segmentIndices}
|
||||
onUserSeek={this.onUserSeek}
|
||||
onPlaySeek={this.props.onPlaySeek}
|
||||
videoElement={this.state.videoElement}
|
||||
onPlay={this.props.onPlay}
|
||||
onPause={this.props.onPause}
|
||||
|
@ -203,3 +191,11 @@ export default class RouteVideoSync extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stateToProps = Obstruction({
|
||||
selectedParts: "playback.selectedParts",
|
||||
seekTime: "playback.seekTime",
|
||||
maxTime: "playback.maxTime"
|
||||
});
|
||||
|
||||
export default connect(stateToProps)(RouteVideoSync);
|
||||
|
|
|
@ -10,13 +10,20 @@ import {
|
|||
fetchPersistedGithubAuthToken,
|
||||
persistGithubAuthToken
|
||||
} from "./api/localstorage";
|
||||
import createStore from "./store";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const store = createStore();
|
||||
Sentry.init();
|
||||
|
||||
const routeFullName = getUrlParameter("route");
|
||||
let isDemo = !routeFullName;
|
||||
let props = { autoplay: true, isDemo };
|
||||
let props = {
|
||||
autoplay: true,
|
||||
isDemo,
|
||||
store
|
||||
};
|
||||
let persistedDbc = null;
|
||||
|
||||
if (routeFullName) {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { combineReducers } from "redux";
|
||||
import playback from "./playback";
|
||||
import route from "./route";
|
||||
import segment from "./segments";
|
||||
|
||||
export default combineReducers({
|
||||
playback: playback,
|
||||
route: route,
|
||||
segment: segment
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
import {
|
||||
ACTION_SEEK,
|
||||
ACTION_SELECT_PART,
|
||||
ACTION_AUTO_SEEK,
|
||||
ACTION_SET_LOADING,
|
||||
ACTION_SET_MAX_TIME
|
||||
} from "../actions/types";
|
||||
import { PART_SEGMENT_LENGTH } from "../config";
|
||||
import { getUrlParameter } from "../utils/url";
|
||||
|
||||
var initialSeekTime = getUrlParameter("seekTime");
|
||||
|
||||
if (initialSeekTime) {
|
||||
initialSeekTime = Number(initialSeekTime);
|
||||
} else {
|
||||
initialSeekTime = 0;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
seekTime: initialSeekTime,
|
||||
selectedParts: [
|
||||
getPartForTime(initialSeekTime),
|
||||
getPartForTime(initialSeekTime) + PART_SEGMENT_LENGTH - 1
|
||||
],
|
||||
maxParts: 0,
|
||||
isLoading: true,
|
||||
seekIndex: 0
|
||||
};
|
||||
|
||||
export default function playback(state, action) {
|
||||
state = doThing(state, action);
|
||||
if (!state.seekTime && state.seekTime !== 0) {
|
||||
debugger;
|
||||
}
|
||||
return state;
|
||||
function doThing(state, action) {
|
||||
if (!state) {
|
||||
return initialState;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ACTION_SEEK:
|
||||
return {
|
||||
...state,
|
||||
seekTime: action.time,
|
||||
userSeekTime: action.time,
|
||||
selectedParts: action.selectedParts,
|
||||
seekIndex: action.index || 0
|
||||
};
|
||||
case ACTION_AUTO_SEEK:
|
||||
// auto-seek from video timestamp updates
|
||||
return {
|
||||
...state,
|
||||
seekTime: action.time
|
||||
};
|
||||
case ACTION_SELECT_PART:
|
||||
return {
|
||||
...state,
|
||||
seekTime: action.seekTime || state.seekTime,
|
||||
selectedParts: action.selectedParts
|
||||
};
|
||||
case ACTION_SET_LOADING:
|
||||
return {
|
||||
...state,
|
||||
isLoading: action.isLoading
|
||||
};
|
||||
case ACTION_SET_MAX_TIME:
|
||||
return {
|
||||
...state,
|
||||
maxTime: action.maxTime
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function getPartForTime(time) {
|
||||
return Math.floor(time / 60);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { ACTION_LOAD_ROUTES, ACTION_SELECT_ROUTE } from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
routes: [],
|
||||
route: null
|
||||
};
|
||||
|
||||
export default function reducer(state, action) {
|
||||
if (!state) {
|
||||
return initialState;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ACTION_LOAD_ROUTES:
|
||||
return {
|
||||
...state,
|
||||
routes: action.routes
|
||||
};
|
||||
case ACTION_SELECT_ROUTE:
|
||||
return {
|
||||
...state,
|
||||
route: action.route,
|
||||
maxParts: action.route.proclog
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { ACTION_SELECT_SEGMENT } from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
segment: [],
|
||||
segmentIndices: []
|
||||
};
|
||||
|
||||
export default function reducer(state, action) {
|
||||
if (!state) {
|
||||
state = initialState;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ACTION_SELECT_SEGMENT:
|
||||
console.log("Set segment", action);
|
||||
return {
|
||||
...state,
|
||||
segment: action.segment,
|
||||
segmentIndices: action.segmentIndices
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import window from "global/window";
|
||||
import * as Redux from "redux";
|
||||
import thunk from "redux-thunk";
|
||||
import * as Actions from "./actions";
|
||||
import reducer from "./reducers";
|
||||
|
||||
let composeEnhancers;
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
) {
|
||||
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||
actionCreators: Object.values(Actions).filter(f => f.name !== "updateState")
|
||||
});
|
||||
} else {
|
||||
composeEnhancers = Redux.compose;
|
||||
}
|
||||
|
||||
export default function createStore() {
|
||||
const store = Redux.createStore(
|
||||
reducer,
|
||||
composeEnhancers(Redux.applyMiddleware(thunk))
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
58
yarn.lock
58
yarn.lock
|
@ -225,7 +225,7 @@ anymatch@^2.0.0:
|
|||
micromatch "^3.1.4"
|
||||
normalize-path "^2.1.1"
|
||||
|
||||
ap@^0.2.0:
|
||||
ap@^0.2.0, ap@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110"
|
||||
|
||||
|
@ -2785,6 +2785,10 @@ dot-prop@^4.1.0:
|
|||
dependencies:
|
||||
is-obj "^1.0.0"
|
||||
|
||||
dot-prop@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-2.1.0.tgz#6bd199d80792d2323a2b7eb8175f4b32d76a7e72"
|
||||
|
||||
dotenv@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-2.0.0.tgz#bd759c357aaa70365e01c96b7b0bec08a6e0d949"
|
||||
|
@ -4137,6 +4141,10 @@ hoek@4.x.x:
|
|||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||
|
||||
hoist-non-react-statics@^2.5.0:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||
|
@ -4450,7 +4458,7 @@ interpret@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||
|
||||
invariant@^2.2.2:
|
||||
invariant@^2.0.0, invariant@^2.2.2:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
dependencies:
|
||||
|
@ -4681,6 +4689,10 @@ is-obj@^1.0.0, is-obj@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||
|
||||
is-object@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
|
||||
|
||||
is-observable@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2"
|
||||
|
@ -5490,6 +5502,10 @@ locate-path@^2.0.0:
|
|||
p-locate "^2.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash-es@^4.17.5:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05"
|
||||
|
||||
lodash._reinterpolate@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
|
@ -5551,7 +5567,7 @@ lodash.uniq@^4.5.0:
|
|||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4:
|
||||
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||
|
||||
|
@ -6259,6 +6275,16 @@ object.values@^1.0.4:
|
|||
function-bind "^1.1.0"
|
||||
has "^1.0.1"
|
||||
|
||||
obstruction@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/obstruction/-/obstruction-2.1.0.tgz#905afff04474bb6f5023979a68161d41eda23be3"
|
||||
dependencies:
|
||||
ap "~0.2.0"
|
||||
dot-prop "~2.1.0"
|
||||
is-object "~1.0.1"
|
||||
isarray "0.0.1"
|
||||
map-obj "~1.0.1"
|
||||
|
||||
obtain-unicode@~0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/obtain-unicode/-/obtain-unicode-0.0.5.tgz#655f337a8f135280495d77a60efc601f9af5d5dc"
|
||||
|
@ -7233,6 +7259,17 @@ react-reconciler@^0.7.0:
|
|||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-redux@^5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^2.5.0"
|
||||
invariant "^2.0.0"
|
||||
lodash "^4.17.5"
|
||||
lodash-es "^4.17.5"
|
||||
loose-envify "^1.1.0"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-scripts@1.0.17:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-1.0.17.tgz#c30029123b561a060227af4d7797d50a222d3fbf"
|
||||
|
@ -7394,6 +7431,17 @@ reduce-function-call@^1.0.1:
|
|||
dependencies:
|
||||
balanced-match "^0.4.2"
|
||||
|
||||
redux-thunk@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
|
||||
redux@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03"
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
||||
regenerate@^1.2.1:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
|
||||
|
@ -8416,6 +8464,10 @@ symbol-observable@^0.2.2:
|
|||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40"
|
||||
|
||||
symbol-observable@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
|
||||
symbol-tree@^3.2.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
|
||||
|
|
Loading…
Reference in New Issue