prettier the js
parent
40e7efad4c
commit
beb0a7ea01
12
package.json
12
package.json
|
@ -17,10 +17,13 @@
|
|||
"github-api": "^3.0.0",
|
||||
"hls": "0.0.1",
|
||||
"hls.js": "^0.7.9",
|
||||
"husky": "^0.14.3",
|
||||
"int64-buffer": "^0.1.9",
|
||||
"js-cookie": "^2.1.4",
|
||||
"left-pad": "^1.1.3",
|
||||
"lint-staged": "^6.0.0",
|
||||
"moment": "^2.18.1",
|
||||
"prettier": "^1.9.2",
|
||||
"prop-types": "^15.5.10",
|
||||
"raven-js": "^3.16.0",
|
||||
"react": "^16.2.0",
|
||||
|
@ -57,6 +60,13 @@
|
|||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test --env=jsdom",
|
||||
"sass":
|
||||
"scss src/index.scss:src/index.css; sass --watch src/index.scss:src/index.css"
|
||||
"scss src/index.scss:src/index.css; sass --watch src/index.scss:src/index.css",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx}": ["prettier --parser flow --write", "git add"],
|
||||
"*.json": ["prettier --parser json --write", "git add"],
|
||||
"*.{graphql,gql}": ["prettier --parser graphql --write", "git add"],
|
||||
"*.{md,markdown}": ["prettier --parser markdown --write", "git add"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
global.__JEST__ = 1
|
||||
import DBC from '../../models/can/dbc';
|
||||
import Signal from '../../models/can/signal';
|
||||
global.__JEST__ = 1;
|
||||
import DBC from "../../models/can/dbc";
|
||||
import Signal from "../../models/can/signal";
|
||||
|
||||
test('setting signals should create a message', () => {
|
||||
const dbc = new DBC();
|
||||
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal'})});
|
||||
test("setting signals should create a message", () => {
|
||||
const dbc = new DBC();
|
||||
dbc.setSignals(100, { "My Signal": new Signal({ name: "My Signal" }) });
|
||||
|
||||
expect(dbc.messages.has(100)).toBe(true);
|
||||
expect(dbc.messages.has(100)).toBe(true);
|
||||
});
|
||||
|
||||
test('setting signals should update DBC.boardUnits', () => {
|
||||
const dbc = new DBC();
|
||||
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal', receiver: ['NEO']})});
|
||||
test("setting signals should update DBC.boardUnits", () => {
|
||||
const dbc = new DBC();
|
||||
dbc.setSignals(100, {
|
||||
"My Signal": new Signal({ name: "My Signal", receiver: ["NEO"] })
|
||||
});
|
||||
|
||||
expect(dbc.boardUnits.map((bu) => bu.name).indexOf('NEO')).toBe(0);
|
||||
expect(dbc.boardUnits.map(bu => bu.name).indexOf("NEO")).toBe(0);
|
||||
});
|
||||
|
||||
test('adding a signal should update DBC.boardUnits', () => {
|
||||
const dbc = new DBC();
|
||||
dbc.createFrame(100);
|
||||
dbc.addSignal(100, new Signal({name: 'My Signal', receiver: ['NEO']}));
|
||||
test("adding a signal should update DBC.boardUnits", () => {
|
||||
const dbc = new DBC();
|
||||
dbc.createFrame(100);
|
||||
dbc.addSignal(100, new Signal({ name: "My Signal", receiver: ["NEO"] }));
|
||||
|
||||
expect(dbc.boardUnits.map((bu) => bu.name).indexOf('NEO')).toBe(0);
|
||||
});
|
||||
expect(dbc.boardUnits.map(bu => bu.name).indexOf("NEO")).toBe(0);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
global.__JEST__ = 1
|
||||
import DBC, {swapOrder} from '../../models/can/dbc';
|
||||
import Signal from '../../models/can/signal';
|
||||
import Bitarray from '../../models/bitarray';
|
||||
global.__JEST__ = 1;
|
||||
import DBC, { swapOrder } from "../../models/can/dbc";
|
||||
import Signal from "../../models/can/signal";
|
||||
import Bitarray from "../../models/bitarray";
|
||||
|
||||
const DBC_MESSAGE_DEF = `BO_ 228 STEERING_CONTROL: 5 ADAS
|
||||
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
|
||||
|
@ -63,7 +63,7 @@ BO_ 228 STEERING_CONTROL: 5 ADAS
|
|||
|
||||
CM_ SG_ 228 STEER_TORQUE "steer torque is the amount of torque in Nm applied";`;
|
||||
|
||||
const DBC_SIGNAL_WITH_MULTI_LINE_COMMENT = `
|
||||
const DBC_SIGNAL_WITH_MULTI_LINE_COMMENT = `
|
||||
BO_ 228 STEERING_CONTROL: 5 ADAS
|
||||
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
|
||||
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
|
||||
|
@ -137,136 +137,148 @@ VAL_TABLE_ DI_speedUnits 1 "DI_SPEED_KPH" 0 "DI_SPEED_MPH" ;
|
|||
`;
|
||||
|
||||
const steerTorqueSignal = new Signal({
|
||||
name: 'STEER_TORQUE',
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
receiver: ['EPS'],
|
||||
unit: ""});
|
||||
|
||||
test('DBC parses steering control message', () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
|
||||
const {signals} = dbcParsed.messages.get(228);
|
||||
|
||||
expect(Object.keys(signals).length).toBe(6);
|
||||
expect(signals['STEER_TORQUE'].equals(steerTorqueSignal)).toBe(true);
|
||||
name: "STEER_TORQUE",
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
receiver: ["EPS"],
|
||||
unit: ""
|
||||
});
|
||||
|
||||
test('DBC parses signal comment', () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
|
||||
const {signals} = dbcParsed.messages.get(228);
|
||||
test("DBC parses steering control message", () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
|
||||
const { signals } = dbcParsed.messages.get(228);
|
||||
|
||||
expect(signals.STEER_TORQUE.comment).toEqual("steer torque is the amount of torque in Nm applied");
|
||||
expect(Object.keys(signals).length).toBe(6);
|
||||
expect(signals["STEER_TORQUE"].equals(steerTorqueSignal)).toBe(true);
|
||||
});
|
||||
|
||||
test('DBC parses multi-line signal comment', () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
|
||||
const {signals} = dbcParsed.messages.get(228);
|
||||
test("DBC parses signal comment", () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
|
||||
const { signals } = dbcParsed.messages.get(228);
|
||||
|
||||
expect(signals.STEER_TORQUE.comment).toEqual("steer torque is the\namount of torque in Nm applied");
|
||||
expect(signals.STEER_TORQUE.comment).toEqual(
|
||||
"steer torque is the amount of torque in Nm applied"
|
||||
);
|
||||
});
|
||||
|
||||
test('DBC parses message comment', () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
|
||||
const msg = dbcParsed.messages.get(228);
|
||||
test("DBC parses multi-line signal comment", () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
|
||||
const { signals } = dbcParsed.messages.get(228);
|
||||
|
||||
expect(msg.comment).toEqual("this message contains steer torque information");
|
||||
expect(signals.STEER_TORQUE.comment).toEqual(
|
||||
"steer torque is the\namount of torque in Nm applied"
|
||||
);
|
||||
});
|
||||
|
||||
test('DBC parses multi-line message comment', () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
|
||||
const msg = dbcParsed.messages.get(228);
|
||||
test("DBC parses message comment", () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
|
||||
const msg = dbcParsed.messages.get(228);
|
||||
|
||||
expect(msg.comment).toEqual("this message contains\nsteer torque information");
|
||||
expect(msg.comment).toEqual("this message contains steer torque information");
|
||||
});
|
||||
|
||||
test('DBC parses board unit names', () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS);
|
||||
expect(dbcParsed.boardUnits[0].name).toEqual("first_board_unit");
|
||||
expect(dbcParsed.boardUnits[1].name).toEqual("second_board_unit");
|
||||
test("DBC parses multi-line message comment", () => {
|
||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
|
||||
const msg = dbcParsed.messages.get(228);
|
||||
|
||||
expect(msg.comment).toEqual(
|
||||
"this message contains\nsteer torque information"
|
||||
);
|
||||
});
|
||||
|
||||
test('DBC parses board unit comments', () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
|
||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
|
||||
test("DBC parses board unit names", () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS);
|
||||
expect(dbcParsed.boardUnits[0].name).toEqual("first_board_unit");
|
||||
expect(dbcParsed.boardUnits[1].name).toEqual("second_board_unit");
|
||||
});
|
||||
|
||||
test('DBC parses multi-line board unit comments', () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
|
||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
|
||||
test("DBC parses board unit comments", () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
|
||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
|
||||
});
|
||||
|
||||
test('DBC parses signal value descriptions', () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
|
||||
const {signals} = dbcParsed.messages.get(228);
|
||||
|
||||
const expectedTorqueRequestVals = new Map([['1', 'requesting torque'],
|
||||
['0', 'not requesting torque']]);
|
||||
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(expectedTorqueRequestVals);
|
||||
test("DBC parses multi-line board unit comments", () => {
|
||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
|
||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
|
||||
});
|
||||
|
||||
test('DBC parses value tables', () => {
|
||||
const dbcParsed = new DBC(DBC_VALUE_TABLE);
|
||||
const stateTableEntries = [['4', "DI_STATE_ENABLE"],
|
||||
['3', "DI_STATE_FAULT"],
|
||||
['2', "DI_STATE_CLEAR_FAULT"],
|
||||
['1', "DI_STATE_STANDBY"],
|
||||
['0', "DI_STATE_PREAUTH"]]
|
||||
const stateTable = new Map(stateTableEntries);
|
||||
const speedUnitsEntries = [['1',"DI_SPEED_KPH"],
|
||||
['0',"DI_SPEED_MPH"]]
|
||||
const speedUnitsTable = new Map(speedUnitsEntries);
|
||||
test("DBC parses signal value descriptions", () => {
|
||||
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
|
||||
const { signals } = dbcParsed.messages.get(228);
|
||||
|
||||
const valueTableEntries = Array.from(dbcParsed.valueTables.entries());
|
||||
expect(valueTableEntries[0]).toEqual(['DI_state', stateTable]);
|
||||
expect(valueTableEntries[1]).toEqual(['DI_speedUnits', speedUnitsTable]);
|
||||
const expectedTorqueRequestVals = new Map([
|
||||
["1", "requesting torque"],
|
||||
["0", "not requesting torque"]
|
||||
]);
|
||||
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(
|
||||
expectedTorqueRequestVals
|
||||
);
|
||||
});
|
||||
|
||||
test('swapOrder properly converts little endian to big endian', () => {
|
||||
const littleEndianHex = 'e2d62a0bd0d3b5e5';
|
||||
const bigEndianHex = 'e5b5d3d00b2ad6e2';
|
||||
test("DBC parses value tables", () => {
|
||||
const dbcParsed = new DBC(DBC_VALUE_TABLE);
|
||||
const stateTableEntries = [
|
||||
["4", "DI_STATE_ENABLE"],
|
||||
["3", "DI_STATE_FAULT"],
|
||||
["2", "DI_STATE_CLEAR_FAULT"],
|
||||
["1", "DI_STATE_STANDBY"],
|
||||
["0", "DI_STATE_PREAUTH"]
|
||||
];
|
||||
const stateTable = new Map(stateTableEntries);
|
||||
const speedUnitsEntries = [["1", "DI_SPEED_KPH"], ["0", "DI_SPEED_MPH"]];
|
||||
const speedUnitsTable = new Map(speedUnitsEntries);
|
||||
|
||||
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
|
||||
|
||||
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
|
||||
const valueTableEntries = Array.from(dbcParsed.valueTables.entries());
|
||||
expect(valueTableEntries[0]).toEqual(["DI_state", stateTable]);
|
||||
expect(valueTableEntries[1]).toEqual(["DI_speedUnits", speedUnitsTable]);
|
||||
});
|
||||
|
||||
test('int32 parser produces correct value for steer torque signal', () => {
|
||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
||||
test("swapOrder properly converts little endian to big endian", () => {
|
||||
const littleEndianHex = "e2d62a0bd0d3b5e5";
|
||||
const bigEndianHex = "e5b5d3d00b2ad6e2";
|
||||
|
||||
const hex = 'e2d62a0bd0d3b5e5';
|
||||
const buffer = Buffer.from(hex, 'hex');
|
||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
||||
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
|
||||
|
||||
const bitArr = Bitarray.fromBytes(buffer);
|
||||
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
||||
const value = dbc.valueForInt32Signal(steerTorqueSignal, bitArr, bitsSwapped);
|
||||
|
||||
expect(value).toBe(-7466);
|
||||
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
|
||||
});
|
||||
|
||||
test('int64 parser produces correct value for steer torque signal', () => {
|
||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
||||
test("int32 parser produces correct value for steer torque signal", () => {
|
||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
||||
|
||||
const hex = 'e2d62a0bd0d3b5e5';
|
||||
const value = dbc.valueForInt64Signal(steerTorqueSignal, hex);
|
||||
const hex = "e2d62a0bd0d3b5e5";
|
||||
const buffer = Buffer.from(hex, "hex");
|
||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
||||
|
||||
expect(value).toBe(-7466);
|
||||
const bitArr = Bitarray.fromBytes(buffer);
|
||||
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
||||
const value = dbc.valueForInt32Signal(steerTorqueSignal, bitArr, bitsSwapped);
|
||||
|
||||
expect(value).toBe(-7466);
|
||||
});
|
||||
|
||||
test("int64 parser produces correct value for steer torque signal", () => {
|
||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
||||
|
||||
const hex = "e2d62a0bd0d3b5e5";
|
||||
const value = dbc.valueForInt64Signal(steerTorqueSignal, hex);
|
||||
|
||||
expect(value).toBe(-7466);
|
||||
});
|
||||
|
||||
function dbcInt32SignalValue(dbc, signalSpec, hex) {
|
||||
const buffer = Buffer.from(hex, 'hex');
|
||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
||||
const buffer = Buffer.from(hex, "hex");
|
||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
||||
|
||||
const bits = Bitarray.fromBytes(buffer);
|
||||
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
||||
const bits = Bitarray.fromBytes(buffer);
|
||||
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
||||
|
||||
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
|
||||
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
|
||||
}
|
||||
|
||||
const DBC_BINARY_LE_SIGNAL = `
|
||||
|
@ -274,56 +286,53 @@ BO_ 768 NEW_MSG_1: 8 XXX
|
|||
SG_ NEW_SIGNAL_1 : 37|1@1+ (1,0) [0|1] "" XXX
|
||||
`;
|
||||
|
||||
test('int32 parsers produces correct value for binary little endian signal', () => {
|
||||
const dbc = new DBC(DBC_BINARY_LE_SIGNAL)
|
||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
||||
test("int32 parsers produces correct value for binary little endian signal", () => {
|
||||
const dbc = new DBC(DBC_BINARY_LE_SIGNAL);
|
||||
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||
|
||||
const hexDataSet = '0000000020000000';
|
||||
const hexDataNotSet = '0000000000000000';
|
||||
const hexDataSet = "0000000020000000";
|
||||
const hexDataNotSet = "0000000000000000";
|
||||
|
||||
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
|
||||
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
|
||||
|
||||
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
|
||||
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
|
||||
|
||||
expect(setValue).toEqual(1);
|
||||
expect(notSetValue).toEqual(0);
|
||||
expect(setValue).toEqual(1);
|
||||
expect(notSetValue).toEqual(0);
|
||||
});
|
||||
|
||||
const DBC_TWO_BIT_LE_SIGNAL = `
|
||||
BO_ 768 NEW_MSG_1: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 35|2@1+ (1,0) [0|3] "" XXX
|
||||
`;
|
||||
test('int32 parser produces correct value for 2-bit little endian signal spanning words', () => {
|
||||
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
|
||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
||||
test("int32 parser produces correct value for 2-bit little endian signal spanning words", () => {
|
||||
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
|
||||
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||
|
||||
const hexData = '00000001f8000000';
|
||||
const hexData = "00000001f8000000";
|
||||
|
||||
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
|
||||
expect(value).toEqual(3);
|
||||
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
|
||||
expect(value).toEqual(3);
|
||||
});
|
||||
|
||||
const DBC_FOUR_BIT_LE_SIGNAL = `
|
||||
BO_ 768 NEW_MSG_1: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 6|4@1+ (1,0) [0|15] "" XXX
|
||||
`;
|
||||
test('int32 parser produces correct value for 4-bit little endian signal', () => {
|
||||
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
|
||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
||||
test("int32 parser produces correct value for 4-bit little endian signal", () => {
|
||||
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
|
||||
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||
|
||||
// this data is symmetric, the data bits are 1111
|
||||
const hexDataSymmetric = 'f00f000000000000';
|
||||
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
|
||||
expect(symValue).toEqual(15);
|
||||
// this data is symmetric, the data bits are 1111
|
||||
const hexDataSymmetric = "f00f000000000000";
|
||||
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
|
||||
expect(symValue).toEqual(15);
|
||||
|
||||
// this data is asymmetric, the data bits are 1101
|
||||
const hexDataAsymmetric = 'f002000000000000';
|
||||
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
|
||||
expect(aSymValue).toEqual(11);
|
||||
// this data is asymmetric, the data bits are 1101
|
||||
const hexDataAsymmetric = "f002000000000000";
|
||||
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
|
||||
expect(aSymValue).toEqual(11);
|
||||
});
|
||||
|
||||
|
||||
|
||||
const DBC_CHFFR_METRIC_COMMENT = `
|
||||
BO_ 37 STEERING_CONTROL: 8 XXX
|
||||
SG_ STEER_ANGLE : 6|4@1+ (1,0) [0|15] "" XXX
|
||||
|
@ -331,10 +340,12 @@ BO_ 37 STEERING_CONTROL: 8 XXX
|
|||
CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180";
|
||||
`;
|
||||
|
||||
test('dbc parser parses top-level comment with chffr metric', () => {
|
||||
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
|
||||
const { comments } = dbc;
|
||||
test("dbc parser parses top-level comment with chffr metric", () => {
|
||||
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
|
||||
const { comments } = dbc;
|
||||
|
||||
expect(comments.length).toEqual(1);
|
||||
expect(comments[0]).toEqual("CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180");
|
||||
})
|
||||
expect(comments.length).toEqual(1);
|
||||
expect(comments[0]).toEqual(
|
||||
"CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
global.__JEST__ = 1
|
||||
import DBC, {swapOrder} from '../../models/can/dbc';
|
||||
import { ACURA_DBC } from '../res/acura-dbc';
|
||||
import { CRV_DBC } from '../res/crv-dbc';
|
||||
import { TESLA_DBC } from '../res/tesla-dbc';
|
||||
global.__JEST__ = 1;
|
||||
import DBC, { swapOrder } from "../../models/can/dbc";
|
||||
import { ACURA_DBC } from "../res/acura-dbc";
|
||||
import { CRV_DBC } from "../res/crv-dbc";
|
||||
import { TESLA_DBC } from "../res/tesla-dbc";
|
||||
|
||||
test('DBC.text() for acura DBC should be equivalent to its original text', () => {
|
||||
const dbc = new DBC(ACURA_DBC);
|
||||
test("DBC.text() for acura DBC should be equivalent to its original text", () => {
|
||||
const dbc = new DBC(ACURA_DBC);
|
||||
|
||||
expect(dbc.text()).toBe(ACURA_DBC);
|
||||
expect(dbc.text()).toBe(ACURA_DBC);
|
||||
});
|
||||
|
||||
test('DBC.text() for crv DBC should be equivalent to its original text', () => {
|
||||
const dbc = new DBC(CRV_DBC);
|
||||
test("DBC.text() for crv DBC should be equivalent to its original text", () => {
|
||||
const dbc = new DBC(CRV_DBC);
|
||||
|
||||
expect(dbc.text()).toBe(CRV_DBC);
|
||||
expect(dbc.text()).toBe(CRV_DBC);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import Entries from '../../models/can/entries';
|
||||
import Entries from "../../models/can/entries";
|
||||
|
||||
test('segment index low is inclusive and index high is exclusive', () => {
|
||||
const entries = [{time: 1.0}, {time: 3.45}, {time: 3.65}, {time: 5.55}];
|
||||
const [segmentIdxLow, segmentIdxHi] = Entries.findSegmentIndices(entries, [3.45, 5.55]);
|
||||
test("segment index low is inclusive and index high is exclusive", () => {
|
||||
const entries = [
|
||||
{ time: 1.0 },
|
||||
{ time: 3.45 },
|
||||
{ time: 3.65 },
|
||||
{ time: 5.55 }
|
||||
];
|
||||
const [segmentIdxLow, segmentIdxHi] = Entries.findSegmentIndices(entries, [
|
||||
3.45,
|
||||
5.55
|
||||
]);
|
||||
|
||||
expect(segmentIdxLow).toBe(1);
|
||||
expect(segmentIdxHi).toBe(entries.length - 1);
|
||||
})
|
||||
expect(segmentIdxLow).toBe(1);
|
||||
expect(segmentIdxHi).toBe(entries.length - 1);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import Frame from '../../models/can/frame';
|
||||
import Frame from "../../models/can/frame";
|
||||
|
||||
const FRAME_HEADER = "BO_ 255 SOME_FRAME: 5 ADAS";
|
||||
|
||||
test('Frame.header() returns spec compliant representation', () => {
|
||||
const frame = new Frame({name: 'SOME_FRAME', id: 255, size: 5, transmitters: ['ADAS']});
|
||||
expect(frame.header()).toEqual(FRAME_HEADER);
|
||||
test("Frame.header() returns spec compliant representation", () => {
|
||||
const frame = new Frame({
|
||||
name: "SOME_FRAME",
|
||||
id: 255,
|
||||
size: 5,
|
||||
transmitters: ["ADAS"]
|
||||
});
|
||||
expect(frame.header()).toEqual(FRAME_HEADER);
|
||||
});
|
||||
|
|
|
@ -1,77 +1,104 @@
|
|||
import Signal from '../../models/can/signal';
|
||||
import Signal from "../../models/can/signal";
|
||||
|
||||
const someSignalParams = {
|
||||
name: 'STEER_TORQUE',
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
unit: ""};
|
||||
name: "STEER_TORQUE",
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
unit: ""
|
||||
};
|
||||
|
||||
const someOtherSignalParams = {
|
||||
name: 'DIFFERENT_NAME',
|
||||
startBit: 0,
|
||||
name: "DIFFERENT_NAME",
|
||||
startBit: 0,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
unit: ""
|
||||
};
|
||||
|
||||
test("Signal.equals returns true for signals with identical properties", () => {
|
||||
const someSignal = new Signal(someSignalParams);
|
||||
const someEquivalentSignal = new Signal(someSignalParams);
|
||||
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
|
||||
});
|
||||
|
||||
test("Signal.equals returns false for signals with different properties", () => {
|
||||
const someSignal = new Signal(someSignalParams);
|
||||
const differentSignal = new Signal(someOtherSignalParams);
|
||||
expect(someSignal.equals(differentSignal)).toBe(false);
|
||||
});
|
||||
|
||||
test("Signal.bitDescription returns proper description for a little endian signal", () => {
|
||||
const littleEndianSignal = new Signal({
|
||||
name: "little endian signal",
|
||||
startBit: 20,
|
||||
size: 4,
|
||||
isLittleEndian: true
|
||||
});
|
||||
|
||||
expect(littleEndianSignal.bitDescription(20).bitNumber).toBe(0);
|
||||
expect(littleEndianSignal.bitDescription(21).bitNumber).toBe(1);
|
||||
expect(littleEndianSignal.bitDescription(22).bitNumber).toBe(2);
|
||||
expect(littleEndianSignal.bitDescription(23).bitNumber).toBe(3);
|
||||
});
|
||||
|
||||
test("Signal.bitDescription returns proper description for a big endian signal", () => {
|
||||
const bigEndianSignal = new Signal({
|
||||
name: "big endian signal",
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false,
|
||||
isSigned: true,
|
||||
factor: 1,
|
||||
offset: 0,
|
||||
min: -3840,
|
||||
max: 3840,
|
||||
unit: ""};
|
||||
isLittleEndian: false
|
||||
});
|
||||
|
||||
test('Signal.equals returns true for signals with identical properties', () => {
|
||||
const someSignal = new Signal(someSignalParams);
|
||||
const someEquivalentSignal = new Signal(someSignalParams);
|
||||
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
|
||||
const bitFifteenDescription = {
|
||||
bitNumber: 8,
|
||||
isLsb: false,
|
||||
isMsb: false,
|
||||
range: [0, 15]
|
||||
};
|
||||
const bitZeroDescription = {
|
||||
bitNumber: 7,
|
||||
isLsb: false,
|
||||
isMsb: false,
|
||||
range: [0, 15]
|
||||
};
|
||||
const bitEightDescription = {
|
||||
bitNumber: 15,
|
||||
isLsb: true,
|
||||
isMsb: false,
|
||||
range: [0, 15]
|
||||
};
|
||||
const bitSevenDescription = {
|
||||
bitNumber: 0,
|
||||
isLsb: false,
|
||||
isMsb: true,
|
||||
range: [0, 15]
|
||||
};
|
||||
|
||||
expect(bigEndianSignal.bitDescription(15)).toEqual(bitFifteenDescription);
|
||||
expect(bigEndianSignal.bitDescription(0)).toEqual(bitZeroDescription);
|
||||
expect(bigEndianSignal.bitDescription(8)).toEqual(bitEightDescription);
|
||||
expect(bigEndianSignal.bitDescription(7)).toEqual(bitSevenDescription);
|
||||
});
|
||||
|
||||
test('Signal.equals returns false for signals with different properties', () => {
|
||||
const someSignal = new Signal(someSignalParams);
|
||||
const differentSignal = new Signal(someOtherSignalParams);
|
||||
expect(someSignal.equals(differentSignal)).toBe(false);
|
||||
});
|
||||
|
||||
test('Signal.bitDescription returns proper description for a little endian signal', () => {
|
||||
const littleEndianSignal = new Signal({name: 'little endian signal',
|
||||
startBit: 20,
|
||||
size: 4,
|
||||
isLittleEndian: true});
|
||||
|
||||
expect(littleEndianSignal.bitDescription(20).bitNumber).toBe(0);
|
||||
expect(littleEndianSignal.bitDescription(21).bitNumber).toBe(1);
|
||||
expect(littleEndianSignal.bitDescription(22).bitNumber).toBe(2);
|
||||
expect(littleEndianSignal.bitDescription(23).bitNumber).toBe(3);
|
||||
});
|
||||
|
||||
test('Signal.bitDescription returns proper description for a big endian signal', () => {
|
||||
const bigEndianSignal = new Signal({name: 'big endian signal',
|
||||
startBit: 7,
|
||||
size: 16,
|
||||
isLittleEndian: false});
|
||||
|
||||
const bitFifteenDescription = {bitNumber: 8, isLsb: false, isMsb: false, range: [0,15]};
|
||||
const bitZeroDescription = {bitNumber: 7, isLsb: false, isMsb: false, range: [0,15]};
|
||||
const bitEightDescription = {bitNumber: 15, isLsb: true, isMsb: false, range: [0,15]};
|
||||
const bitSevenDescription = {bitNumber: 0, isLsb: false, isMsb: true, range: [0,15]};
|
||||
|
||||
expect(bigEndianSignal.bitDescription(15)).toEqual(bitFifteenDescription);
|
||||
expect(bigEndianSignal.bitDescription(0)).toEqual(bitZeroDescription);
|
||||
expect(bigEndianSignal.bitDescription(8)).toEqual(bitEightDescription);
|
||||
expect(bigEndianSignal.bitDescription(7)).toEqual(bitSevenDescription);
|
||||
});
|
||||
|
||||
|
||||
test('Signal.bitDescription returns null for bit index that is not in its range', () => {
|
||||
const someSignal = new Signal({name: 'some signal',
|
||||
startBit: 20,
|
||||
size: 4,
|
||||
isLittleEndian: false});
|
||||
|
||||
expect(someSignal.bitDescription(21)).toBe(null);
|
||||
expect(someSignal.bitDescription(16)).toBe(null);
|
||||
test("Signal.bitDescription returns null for bit index that is not in its range", () => {
|
||||
const someSignal = new Signal({
|
||||
name: "some signal",
|
||||
startBit: 20,
|
||||
size: 4,
|
||||
isLittleEndian: false
|
||||
});
|
||||
|
||||
expect(someSignal.bitDescription(21)).toBe(null);
|
||||
expect(someSignal.bitDescription(16)).toBe(null);
|
||||
});
|
||||
|
|
|
@ -5,10 +5,10 @@ note: 'right' and 'left' in test descriptions
|
|||
refer to the sides of the bit matrix
|
||||
as displayed to the user.
|
||||
*/
|
||||
import AddSignals from '../../components/AddSignals';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import {StyleSheetTestUtils} from 'aphrodite';
|
||||
import AddSignals from "../../components/AddSignals";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
// Prevents style injection from firing after test finishes
|
||||
// and jsdom is torn down.
|
||||
|
@ -22,286 +22,332 @@ afterEach(() => {
|
|||
// signal creation
|
||||
|
||||
function createAddSignals(signals) {
|
||||
if(signals === undefined) {
|
||||
signals = {};
|
||||
}
|
||||
const message = {signals,
|
||||
address: 0,
|
||||
entries: [
|
||||
{relTime: 0,
|
||||
hexData: '0000000000000000'}
|
||||
]};
|
||||
if (signals === undefined) {
|
||||
signals = {};
|
||||
}
|
||||
const message = {
|
||||
signals,
|
||||
address: 0,
|
||||
entries: [
|
||||
{
|
||||
relTime: 0,
|
||||
hexData: "0000000000000000"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const component = shallow(<AddSignals
|
||||
message={message}
|
||||
messageIndex={0}
|
||||
onConfirmedSignalChange={() => {}} />);
|
||||
const component = shallow(
|
||||
<AddSignals
|
||||
message={message}
|
||||
messageIndex={0}
|
||||
onConfirmedSignalChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
return component;
|
||||
}
|
||||
test('double clicking adds a signal', () => {
|
||||
const component = createAddSignals();
|
||||
test("double clicking adds a signal", () => {
|
||||
const component = createAddSignals();
|
||||
|
||||
const firstBit = component.find('.bit').first();
|
||||
const firstBit = component.find(".bit").first();
|
||||
|
||||
firstBit.simulate('dblclick');
|
||||
const newSignal = Object.values(component.state('signals'))[0];
|
||||
firstBit.simulate("dblclick");
|
||||
const newSignal = Object.values(component.state("signals"))[0];
|
||||
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(1);
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(1);
|
||||
});
|
||||
|
||||
test('dragging right to left across a byte creates a little endian signal', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find('.bit').first();
|
||||
const rightBitInByte = component.find('.bit').at(7);
|
||||
rightBitInByte.simulate('mousedown');
|
||||
leftBitInByte.simulate('mouseup');
|
||||
test("dragging right to left across a byte creates a little endian signal", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find(".bit").first();
|
||||
const rightBitInByte = component.find(".bit").at(7);
|
||||
rightBitInByte.simulate("mousedown");
|
||||
leftBitInByte.simulate("mouseup");
|
||||
|
||||
const newSignal = Object.values(component.state('signals'))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(8);
|
||||
expect(newSignal.isLittleEndian).toBe(true);
|
||||
expect(newSignal.startBit).toBe(0);
|
||||
const newSignal = Object.values(component.state("signals"))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(8);
|
||||
expect(newSignal.isLittleEndian).toBe(true);
|
||||
expect(newSignal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging left to right across a byte creates a big endian signal', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find('.bit').first();
|
||||
const rightBitInByte = component.find('.bit').at(7);
|
||||
leftBitInByte.simulate('mousedown');
|
||||
rightBitInByte.simulate('mouseup');
|
||||
test("dragging left to right across a byte creates a big endian signal", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find(".bit").first();
|
||||
const rightBitInByte = component.find(".bit").at(7);
|
||||
leftBitInByte.simulate("mousedown");
|
||||
rightBitInByte.simulate("mouseup");
|
||||
|
||||
const newSignal = Object.values(component.state('signals'))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(8);
|
||||
expect(newSignal.isLittleEndian).toBe(false);
|
||||
expect(newSignal.startBit).toBe(7);
|
||||
const newSignal = Object.values(component.state("signals"))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(8);
|
||||
expect(newSignal.isLittleEndian).toBe(false);
|
||||
expect(newSignal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('dragging from the left of byte 0 to right of byte 1 creates a big endian signal spanning both bytes', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find('.bit').first();
|
||||
const rightBitInByte = component.find('.bit').at(15);
|
||||
leftBitInByte.simulate('mousedown');
|
||||
rightBitInByte.simulate('mouseup');
|
||||
test("dragging from the left of byte 0 to right of byte 1 creates a big endian signal spanning both bytes", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByte = component.find(".bit").first();
|
||||
const rightBitInByte = component.find(".bit").at(15);
|
||||
leftBitInByte.simulate("mousedown");
|
||||
rightBitInByte.simulate("mouseup");
|
||||
|
||||
const newSignal = Object.values(component.state('signals'))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(16);
|
||||
expect(newSignal.isLittleEndian).toBe(false);
|
||||
expect(newSignal.startBit).toBe(7);
|
||||
const newSignal = Object.values(component.state("signals"))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(16);
|
||||
expect(newSignal.isLittleEndian).toBe(false);
|
||||
expect(newSignal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('dragging from the right of byte 0 to the left of byte 1 creates a little endian signal spanning both bytes', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteOne = component.find('.bit').at(8); // left of byte 1
|
||||
const rightBitInByteZero = component.find('.bit').at(7); // right of byte 0
|
||||
test("dragging from the right of byte 0 to the left of byte 1 creates a little endian signal spanning both bytes", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteOne = component.find(".bit").at(8); // left of byte 1
|
||||
const rightBitInByteZero = component.find(".bit").at(7); // right of byte 0
|
||||
|
||||
rightBitInByteZero.simulate('mousedown');
|
||||
leftBitInByteOne.simulate('mouseup');
|
||||
rightBitInByteZero.simulate("mousedown");
|
||||
leftBitInByteOne.simulate("mouseup");
|
||||
|
||||
const newSignal = Object.values(component.state('signals'))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(16);
|
||||
expect(newSignal.isLittleEndian).toBe(true);
|
||||
expect(newSignal.startBit).toBe(0);
|
||||
const newSignal = Object.values(component.state("signals"))[0];
|
||||
expect(newSignal).toBeDefined();
|
||||
expect(newSignal.size).toBe(16);
|
||||
expect(newSignal.isLittleEndian).toBe(true);
|
||||
expect(newSignal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging from the left of byte 1 to the right of byte 0 creates a little endian signal spanning both bytes', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteOne = component.find('.bit').at(8);
|
||||
const rightBitInByteZero = component.find('.bit').at(7);
|
||||
test("dragging from the left of byte 1 to the right of byte 0 creates a little endian signal spanning both bytes", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteOne = component.find(".bit").at(8);
|
||||
const rightBitInByteZero = component.find(".bit").at(7);
|
||||
|
||||
leftBitInByteOne.simulate('mousedown');
|
||||
rightBitInByteZero.simulate('mouseup');
|
||||
leftBitInByteOne.simulate("mousedown");
|
||||
rightBitInByteZero.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(16);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(16);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging from the right of byte 1 to the left of byte 0 creates a big endian signal spanning both bytes', () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteZero = component.find('.bit').at(0);
|
||||
const rightBitInByteOne = component.find('.bit').at(15);
|
||||
test("dragging from the right of byte 1 to the left of byte 0 creates a big endian signal spanning both bytes", () => {
|
||||
const component = createAddSignals();
|
||||
const leftBitInByteZero = component.find(".bit").at(0);
|
||||
const rightBitInByteOne = component.find(".bit").at(15);
|
||||
|
||||
rightBitInByteOne.simulate('mousedown');
|
||||
leftBitInByteZero.simulate('mouseup');
|
||||
rightBitInByteOne.simulate("mousedown");
|
||||
leftBitInByteZero.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(16);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(16);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
// signal mutation
|
||||
|
||||
test('dragging a one-bit big-endian signal to the right should extend it to the right of the byte', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: false});
|
||||
test("dragging a one-bit big-endian signal to the right should extend it to the right of the byte", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 7, size: 1, isLittleEndian: false });
|
||||
|
||||
const signalBit = component.find('.bit').at(0);
|
||||
signalBit.simulate('mousedown');
|
||||
for(let i = 1; i < 8; i++) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
||||
bitAtRightOfFirstByte.simulate('mouseup');
|
||||
const signalBit = component.find(".bit").at(0);
|
||||
signalBit.simulate("mousedown");
|
||||
for (let i = 1; i < 8; i++) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||
bitAtRightOfFirstByte.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('dragging a one-bit little-endian signal to the right should extend it to the right of the byte', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: true});
|
||||
test("dragging a one-bit little-endian signal to the right should extend it to the right of the byte", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 7, size: 1, isLittleEndian: true });
|
||||
|
||||
const signalBit = component.find('.bit').at(0);
|
||||
signalBit.simulate('mousedown');
|
||||
for(let i = 1; i < 8; i++) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
||||
bitAtRightOfFirstByte.simulate('mouseup');
|
||||
const signalBit = component.find(".bit").at(0);
|
||||
signalBit.simulate("mousedown");
|
||||
for (let i = 1; i < 8; i++) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||
bitAtRightOfFirstByte.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging a one-bit big-endian signal to the left should extend it to the left of the byte', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 0, size: 1, isLittleEndian: false});
|
||||
test("dragging a one-bit big-endian signal to the left should extend it to the left of the byte", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 0, size: 1, isLittleEndian: false });
|
||||
|
||||
const signalBit = component.find('.bit').at(7);
|
||||
signalBit.simulate('mousedown');
|
||||
for(let i = 6; i > -1; i--) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find('.bit').at(0);
|
||||
bitAtRightOfFirstByte.simulate('mouseup');
|
||||
const signalBit = component.find(".bit").at(7);
|
||||
signalBit.simulate("mousedown");
|
||||
for (let i = 6; i > -1; i--) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find(".bit").at(0);
|
||||
bitAtRightOfFirstByte.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('extending a two-bit big-endian signal by its LSB should extend it to the right of the byte', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 7, size: 2, isLittleEndian: false});
|
||||
test("extending a two-bit big-endian signal by its LSB should extend it to the right of the byte", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 7, size: 2, isLittleEndian: false });
|
||||
|
||||
const lsb = component.find('.bit').at(1);
|
||||
lsb.simulate('mousedown');
|
||||
for(let i = 0; i < 8; i++) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
||||
bitAtRightOfFirstByte.simulate('mouseup');
|
||||
const lsb = component.find(".bit").at(1);
|
||||
lsb.simulate("mousedown");
|
||||
for (let i = 0; i < 8; i++) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||
bitAtRightOfFirstByte.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('a two-bit little-endian signal should extend by its LSB to the end of the byte', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 6, size: 2, isLittleEndian: true});
|
||||
test("a two-bit little-endian signal should extend by its LSB to the end of the byte", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 6, size: 2, isLittleEndian: true });
|
||||
|
||||
const lsb = component.find('.bit').at(1);
|
||||
lsb.simulate('mousedown');
|
||||
for(let i = 0; i < 8; i++) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
||||
bitAtRightOfFirstByte.simulate('mouseup');
|
||||
const lsb = component.find(".bit").at(1);
|
||||
lsb.simulate("mousedown");
|
||||
for (let i = 0; i < 8; i++) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||
bitAtRightOfFirstByte.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
|
||||
test("dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
|
||||
|
||||
const lsb = component.find('.bit').at(7);
|
||||
lsb.simulate('mousedown');
|
||||
const lsb = component.find(".bit").at(7);
|
||||
lsb.simulate("mousedown");
|
||||
|
||||
const bitPastMsb = component.find('.bit').at(15);
|
||||
bitPastMsb.simulate('mouseenter');
|
||||
bitPastMsb.simulate('mouseup');
|
||||
const bitPastMsb = component.find(".bit").at(15);
|
||||
bitPastMsb.simulate("mouseenter");
|
||||
bitPastMsb.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 7, size: 8, isLittleEndian: false});
|
||||
test("dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 7, size: 8, isLittleEndian: false });
|
||||
|
||||
const lsb = component.find('.bit').at(7);
|
||||
lsb.simulate('mousedown');
|
||||
for(let i = 6; i > 0; i--) {
|
||||
component.find('.bit').at(i).simulate('mouseenter');
|
||||
}
|
||||
component.find('.bit').at(1).simulate('mouseup');
|
||||
const lsb = component.find(".bit").at(7);
|
||||
lsb.simulate("mousedown");
|
||||
for (let i = 6; i > 0; i--) {
|
||||
component
|
||||
.find(".bit")
|
||||
.at(i)
|
||||
.simulate("mouseenter");
|
||||
}
|
||||
component
|
||||
.find(".bit")
|
||||
.at(1)
|
||||
.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(2);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(2);
|
||||
expect(signal.isLittleEndian).toBe(false);
|
||||
expect(signal.startBit).toBe(7);
|
||||
});
|
||||
|
||||
test('a big endian signal spanning one byte should switch to little endian preserving its bit coverage', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
|
||||
test("a big endian signal spanning one byte should switch to little endian preserving its bit coverage", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
|
||||
|
||||
const lsb = component.find('.bit').at(7);
|
||||
lsb.simulate('mousedown');
|
||||
const lsb = component.find(".bit").at(7);
|
||||
lsb.simulate("mousedown");
|
||||
|
||||
const bitPastMsb = component.find('.bit').at(15);
|
||||
bitPastMsb.simulate('mouseenter');
|
||||
bitPastMsb.simulate('mouseup');
|
||||
const bitPastMsb = component.find(".bit").at(15);
|
||||
bitPastMsb.simulate("mouseenter");
|
||||
bitPastMsb.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(8);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(0);
|
||||
});
|
||||
|
||||
test('dragging the msb of a 2-bit little endian signal to a lower byte should not change the signal', () => {
|
||||
const component = createAddSignals();
|
||||
component.instance().createSignal({startBit: 14, size: 2, isLittleEndian: true});
|
||||
test("dragging the msb of a 2-bit little endian signal to a lower byte should not change the signal", () => {
|
||||
const component = createAddSignals();
|
||||
component
|
||||
.instance()
|
||||
.createSignal({ startBit: 14, size: 2, isLittleEndian: true });
|
||||
|
||||
const msb = component.find('.bit').at(8);
|
||||
msb.simulate('mousedown');
|
||||
const bitOutOfBounds = component.find('.bit').at(0);
|
||||
bitOutOfBounds.simulate('mouseenter');
|
||||
bitOutOfBounds.simulate('mouseup');
|
||||
const msb = component.find(".bit").at(8);
|
||||
msb.simulate("mousedown");
|
||||
const bitOutOfBounds = component.find(".bit").at(0);
|
||||
bitOutOfBounds.simulate("mouseenter");
|
||||
bitOutOfBounds.simulate("mouseup");
|
||||
|
||||
const signal = Object.values(component.state('signals'))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(2);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(14);
|
||||
const signal = Object.values(component.state("signals"))[0];
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal.size).toBe(2);
|
||||
expect(signal.isLittleEndian).toBe(true);
|
||||
expect(signal.startBit).toBe(14);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
global.__JEST__ = 1
|
||||
import CanExplorer from '../../CanExplorer';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import {StyleSheetTestUtils} from 'aphrodite';
|
||||
global.__JEST__ = 1;
|
||||
import CanExplorer from "../../CanExplorer";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
test('CanExplorer renders', () => {
|
||||
const canExplorer = shallow(<CanExplorer />);
|
||||
});
|
||||
test("CanExplorer renders", () => {
|
||||
const canExplorer = shallow(<CanExplorer />);
|
||||
});
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import CanGraph from '../../components/CanGraph';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import CanGraph from "../../components/CanGraph";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
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}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import CanGraphList from '../../components/CanGraphList';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import CanGraphList from "../../components/CanGraphList";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('CanGraphList successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<CanGraphList
|
||||
plottedSignals={[]}
|
||||
messages={{}}
|
||||
graphData={[]}
|
||||
onGraphTimeClick={() => {}}
|
||||
seekTime={0}
|
||||
onSegmentChanged={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
segment={[]}
|
||||
mergePlots={() => {}}
|
||||
live={true} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("CanGraphList successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<CanGraphList
|
||||
plottedSignals={[]}
|
||||
messages={{}}
|
||||
graphData={[]}
|
||||
onGraphTimeClick={() => {}}
|
||||
seekTime={0}
|
||||
onSegmentChanged={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
segment={[]}
|
||||
mergePlots={() => {}}
|
||||
live={true}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import CanLog from '../../components/CanLog';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import CanLog from "../../components/CanLog";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('CanLog successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<CanLog
|
||||
message={null}
|
||||
messageIndex={0}
|
||||
segmentIndices={[]}
|
||||
plottedSignals={[]}
|
||||
onSignalPlotPressed={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
showAddSignal={() => {}}
|
||||
onMessageExpanded={() => {}} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("CanLog successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<CanLog
|
||||
message={null}
|
||||
messageIndex={0}
|
||||
segmentIndices={[]}
|
||||
plottedSignals={[]}
|
||||
onSignalPlotPressed={() => {}}
|
||||
onSignalUnplotPressed={() => {}}
|
||||
showAddSignal={() => {}}
|
||||
onMessageExpanded={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import DbcUpload from '../../components/DbcUpload';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import DbcUpload from "../../components/DbcUpload";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('DbcUpload successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<DbcUpload />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("DbcUpload successfully mounts with minimal default props", () => {
|
||||
const component = shallow(<DbcUpload />);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import EditMessageModal from '../../components/EditMessageModal';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import DbcUtils from '../../utils/dbc';
|
||||
import DBC from '../../models/can/dbc';
|
||||
import EditMessageModal from "../../components/EditMessageModal";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
import DbcUtils from "../../utils/dbc";
|
||||
import DBC from "../../models/can/dbc";
|
||||
|
||||
test('EditMessageModal successfully mounts with minimal default props', () => {
|
||||
const dbc = new DBC();
|
||||
const frame = dbc.createFrame(0);
|
||||
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 1);
|
||||
test("EditMessageModal successfully mounts with minimal default props", () => {
|
||||
const dbc = new DBC();
|
||||
const frame = dbc.createFrame(0);
|
||||
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 1);
|
||||
|
||||
const component = shallow(<EditMessageModal
|
||||
handleClose={() => {}}
|
||||
handleSave={() => {}}
|
||||
message={message}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
const component = shallow(
|
||||
<EditMessageModal
|
||||
handleClose={() => {}}
|
||||
handleSave={() => {}}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import Explorer from '../../components/Explorer';
|
||||
import React from 'react';
|
||||
import Moment from 'moment';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import Explorer from "../../components/Explorer";
|
||||
import React from "react";
|
||||
import Moment from "moment";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
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}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import GithubDbcList from '../../components/GithubDbcList';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import GithubDbcList from "../../components/GithubDbcList";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import OpenDbc from '../../api/OpenDbc';
|
||||
import OpenDbc from "../../api/OpenDbc";
|
||||
|
||||
test('GithubDbcList successfully mounts with minimal default props', () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
test("GithubDbcList successfully mounts with minimal default props", () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
|
||||
const component = shallow(<GithubDbcList
|
||||
onDbcLoaded={ () => {} }
|
||||
repo="commaai/opendbc"
|
||||
openDbcClient={ openDbcClient } />);
|
||||
expect(component.exists()).toBe(true);
|
||||
const component = shallow(
|
||||
<GithubDbcList
|
||||
onDbcLoaded={() => {}}
|
||||
repo="commaai/opendbc"
|
||||
openDbcClient={openDbcClient}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import HLS from '../../components/HLS';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import HLS from "../../components/HLS";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
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={() => {}} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
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={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import LoadDbcModal from '../../components/LoadDbcModal';
|
||||
import OpenDbc from '../../api/OpenDbc';
|
||||
import LoadDbcModal from "../../components/LoadDbcModal";
|
||||
import OpenDbc from "../../api/OpenDbc";
|
||||
|
||||
test('LoadDbcModal successfully mounts with minimal default props', () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
const component = shallow(<LoadDbcModal
|
||||
onDbcSelected={() => {}}
|
||||
handleClose={() => {}}
|
||||
openDbcClient={openDbcClient}
|
||||
loginWithGithub={<p>Login with github</p>}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("LoadDbcModal successfully mounts with minimal default props", () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
const component = shallow(
|
||||
<LoadDbcModal
|
||||
onDbcSelected={() => {}}
|
||||
handleClose={() => {}}
|
||||
openDbcClient={openDbcClient}
|
||||
loginWithGithub={<p>Login with github</p>}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import LoadingBar from '../../components/LoadingBar';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import LoadingBar from "../../components/LoadingBar";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('LoadingBar successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<LoadingBar />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("LoadingBar successfully mounts with minimal default props", () => {
|
||||
const component = shallow(<LoadingBar />);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import MessageBytes from '../../components/MessageBytes';
|
||||
import DbcUtils from '../../utils/dbc';
|
||||
import DBC from '../../models/can/dbc';
|
||||
import MessageBytes from "../../components/MessageBytes";
|
||||
import DbcUtils from "../../utils/dbc";
|
||||
import DBC from "../../models/can/dbc";
|
||||
|
||||
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} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
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} />
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import Meta from '../../components/Meta';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import Meta from "../../components/Meta";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
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}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import Modal from '../../components/Modal';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import Modal from "../../components/Modal";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('Modal successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<Modal />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("Modal successfully mounts with minimal default props", () => {
|
||||
const component = shallow(<Modal />);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import PartSelector from '../../components/PartSelector';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import PartSelector from "../../components/PartSelector";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('PartSelector successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<PartSelector
|
||||
onPartChange={ () => {} }
|
||||
partsCount={ 0 } />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("PartSelector successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<PartSelector onPartChange={() => {}} partsCount={0} />
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import PlayButton from '../../components/PlayButton';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import PlayButton from "../../components/PlayButton";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('PlayButton successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<PlayButton />);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("PlayButton successfully mounts with minimal default props", () => {
|
||||
const component = shallow(<PlayButton />);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import RouteSeeker from '../../components/RouteSeeker';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import RouteSeeker from "../../components/RouteSeeker";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
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={() => {}} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
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={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import RouteVideoSync from '../../components/RouteVideoSync';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import {StyleSheetTestUtils} from 'aphrodite';
|
||||
import RouteVideoSync from "../../components/RouteVideoSync";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
// Prevents style injection from firing after test finishes
|
||||
// and jsdom is torn down.
|
||||
|
@ -14,22 +14,25 @@ afterEach(() => {
|
|||
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
|
||||
});
|
||||
|
||||
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} />);
|
||||
expect(component.exists()).toBe(true);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
import SaveDbcModal from '../../components/SaveDbcModal';
|
||||
import OpenDbc from '../../api/OpenDbc';
|
||||
import DBC from '../../models/can/dbc';
|
||||
import SaveDbcModal from "../../components/SaveDbcModal";
|
||||
import OpenDbc from "../../api/OpenDbc";
|
||||
import DBC from "../../models/can/dbc";
|
||||
|
||||
test('SaveDbcModal successfully mounts with minimal default props', () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
const dbc = new DBC();
|
||||
const component = shallow(<SaveDbcModal
|
||||
dbc={dbc}
|
||||
sourceDbcFilename={''}
|
||||
onDbcSaved={() => {}}
|
||||
handleClose={() => {}}
|
||||
openDbcClient={openDbcClient}
|
||||
hasGithubAuth={false}
|
||||
loginWithGithub={<p>Login with github</p>}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("SaveDbcModal successfully mounts with minimal default props", () => {
|
||||
const openDbcClient = new OpenDbc(null);
|
||||
const dbc = new DBC();
|
||||
const component = shallow(
|
||||
<SaveDbcModal
|
||||
dbc={dbc}
|
||||
sourceDbcFilename={""}
|
||||
onDbcSaved={() => {}}
|
||||
handleClose={() => {}}
|
||||
openDbcClient={openDbcClient}
|
||||
hasGithubAuth={false}
|
||||
loginWithGithub={<p>Login with github</p>}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import SignalLegend from '../../components/SignalLegend';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import SignalLegend from "../../components/SignalLegend";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
|
||||
test('SignalLegend successfully mounts with minimal default props', () => {
|
||||
const component = shallow(<SignalLegend
|
||||
signals={{}}
|
||||
signalStyles={{}}
|
||||
highlightedSignal={null}
|
||||
onSignalHover={() => {}}
|
||||
onSignalHoverEnd={() => {}}
|
||||
onTentativeSignalChange={() => {}}
|
||||
onSignalChange={() => {}}
|
||||
onSignalRemove={() => {}}
|
||||
onSignalPlotChange={() => {}}
|
||||
plottedSignals={[]}
|
||||
/>);
|
||||
expect(component.exists()).toBe(true);
|
||||
test("SignalLegend successfully mounts with minimal default props", () => {
|
||||
const component = shallow(
|
||||
<SignalLegend
|
||||
signals={{}}
|
||||
signalStyles={{}}
|
||||
highlightedSignal={null}
|
||||
onSignalHover={() => {}}
|
||||
onSignalHoverEnd={() => {}}
|
||||
onTentativeSignalChange={() => {}}
|
||||
onSignalChange={() => {}}
|
||||
onSignalRemove={() => {}}
|
||||
onSignalPlotChange={() => {}}
|
||||
plottedSignals={[]}
|
||||
/>
|
||||
);
|
||||
expect(component.exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import SignalLegendEntry from '../../components/SignalLegendEntry';
|
||||
import Signal from '../../models/can/signal';
|
||||
import React from 'react';
|
||||
import { shallow, mount, render } from 'enzyme';
|
||||
import {StyleSheetTestUtils} from 'aphrodite';
|
||||
import SignalLegendEntry from "../../components/SignalLegendEntry";
|
||||
import Signal from "../../models/can/signal";
|
||||
import React from "react";
|
||||
import { shallow, mount, render } from "enzyme";
|
||||
import { StyleSheetTestUtils } from "aphrodite";
|
||||
|
||||
// Prevents style injection from firing after test finishes
|
||||
// and jsdom is torn down.
|
||||
|
@ -14,100 +14,122 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
function createSignalLegendEntry(props) {
|
||||
let signal = props.signal,
|
||||
onSignalChange = props.onSignalChange,
|
||||
onTentativeSignalChange = props.onTentativeSignalChange;
|
||||
if(signal === undefined) {
|
||||
signal = new Signal({name: 'NEW_SIGNAL'});
|
||||
}
|
||||
if(onSignalChange === undefined) {
|
||||
onSignalChange = () => {};
|
||||
}
|
||||
if(onTentativeSignalChange === undefined) {
|
||||
onTentativeSignalChange = () => {};
|
||||
}
|
||||
let signal = props.signal,
|
||||
onSignalChange = props.onSignalChange,
|
||||
onTentativeSignalChange = props.onTentativeSignalChange;
|
||||
if (signal === undefined) {
|
||||
signal = new Signal({ name: "NEW_SIGNAL" });
|
||||
}
|
||||
if (onSignalChange === undefined) {
|
||||
onSignalChange = () => {};
|
||||
}
|
||||
if (onTentativeSignalChange === undefined) {
|
||||
onTentativeSignalChange = () => {};
|
||||
}
|
||||
|
||||
return shallow(<SignalLegendEntry
|
||||
highlightedStyle={null}
|
||||
signal={signal}
|
||||
onSignalChange={onSignalChange}
|
||||
onTentativeSignalChange={onTentativeSignalChange}
|
||||
/>);
|
||||
return shallow(
|
||||
<SignalLegendEntry
|
||||
highlightedStyle={null}
|
||||
signal={signal}
|
||||
onSignalChange={onSignalChange}
|
||||
onTentativeSignalChange={onTentativeSignalChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
test('a little endian signal spanning one byte should adjust its startBit switching to big endian, preserving its bit coverage', () => {
|
||||
const signal = new Signal({name: 'signal',
|
||||
startBit: 0,
|
||||
size: 8,
|
||||
isLittleEndian: true});
|
||||
test("a little endian signal spanning one byte should adjust its startBit switching to big endian, preserving its bit coverage", () => {
|
||||
const signal = new Signal({
|
||||
name: "signal",
|
||||
startBit: 0,
|
||||
size: 8,
|
||||
isLittleEndian: true
|
||||
});
|
||||
|
||||
const component = createSignalLegendEntry({signal});
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
||||
component.instance().updateField(endiannessFieldSpec, false);
|
||||
const component = createSignalLegendEntry({ signal });
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||
"isLittleEndian"
|
||||
);
|
||||
component.instance().updateField(endiannessFieldSpec, false);
|
||||
|
||||
const signalEdited = component.state('signalEdited');
|
||||
expect(signalEdited.isLittleEndian).toBe(false);
|
||||
expect(signalEdited.startBit).toBe(7);
|
||||
expect(signalEdited.size).toBe(8);
|
||||
const signalEdited = component.state("signalEdited");
|
||||
expect(signalEdited.isLittleEndian).toBe(false);
|
||||
expect(signalEdited.startBit).toBe(7);
|
||||
expect(signalEdited.size).toBe(8);
|
||||
});
|
||||
|
||||
test('a big endian signal spanning two bytes should should adjust its startBit switching to little endian, preserving its bit coverage', () => {
|
||||
const signal = new Signal({name: 'signal',
|
||||
startBit: 7,
|
||||
size: 8,
|
||||
isLittleEndian: false});
|
||||
const component = createSignalLegendEntry({signal});
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
test("a big endian signal spanning two bytes should should adjust its startBit switching to little endian, preserving its bit coverage", () => {
|
||||
const signal = new Signal({
|
||||
name: "signal",
|
||||
startBit: 7,
|
||||
size: 8,
|
||||
isLittleEndian: false
|
||||
});
|
||||
const component = createSignalLegendEntry({ signal });
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||
"isLittleEndian"
|
||||
);
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
|
||||
const signalEdited = component.state('signalEdited');
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(0);
|
||||
expect(signalEdited.size).toBe(8);
|
||||
const signalEdited = component.state("signalEdited");
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(0);
|
||||
expect(signalEdited.size).toBe(8);
|
||||
});
|
||||
|
||||
test("a big endian signal spanning one and a half bytes should adjust its startBit switching to little endian, preserving the first byte's coverage", () => {
|
||||
const signal = new Signal({name: 'signal',
|
||||
startBit: 7,
|
||||
size: 12,
|
||||
isLittleEndian: false});
|
||||
const signal = new Signal({
|
||||
name: "signal",
|
||||
startBit: 7,
|
||||
size: 12,
|
||||
isLittleEndian: false
|
||||
});
|
||||
|
||||
const component = createSignalLegendEntry({signal});
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
const component = createSignalLegendEntry({ signal });
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||
"isLittleEndian"
|
||||
);
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
|
||||
const signalEdited = component.state('signalEdited');
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(0);
|
||||
expect(signalEdited.size).toBe(12);
|
||||
const signalEdited = component.state("signalEdited");
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(0);
|
||||
expect(signalEdited.size).toBe(12);
|
||||
});
|
||||
|
||||
test('a little endian signal spanning 3 bits on one byte should adjust its startBit switching to big endian, preserving its bit coverage', () => {
|
||||
const signal = new Signal({name: 'signal',
|
||||
startBit: 13,
|
||||
size: 3,
|
||||
isLittleEndian: true});
|
||||
const component = createSignalLegendEntry({signal});
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
||||
component.instance().updateField(endiannessFieldSpec, false);
|
||||
test("a little endian signal spanning 3 bits on one byte should adjust its startBit switching to big endian, preserving its bit coverage", () => {
|
||||
const signal = new Signal({
|
||||
name: "signal",
|
||||
startBit: 13,
|
||||
size: 3,
|
||||
isLittleEndian: true
|
||||
});
|
||||
const component = createSignalLegendEntry({ signal });
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||
"isLittleEndian"
|
||||
);
|
||||
component.instance().updateField(endiannessFieldSpec, false);
|
||||
|
||||
const signalEdited = component.state('signalEdited');
|
||||
expect(signalEdited.isLittleEndian).toBe(false);
|
||||
expect(signalEdited.startBit).toBe(15);
|
||||
expect(signalEdited.size).toBe(3);
|
||||
const signalEdited = component.state("signalEdited");
|
||||
expect(signalEdited.isLittleEndian).toBe(false);
|
||||
expect(signalEdited.startBit).toBe(15);
|
||||
expect(signalEdited.size).toBe(3);
|
||||
});
|
||||
|
||||
test('a big endian signal spanning 3 bytes on one byte should adjust its startBit switching to little endian, preserving its bit coverage', () => {
|
||||
const signal = new Signal({name: 'signal',
|
||||
startBit: 15,
|
||||
size: 3,
|
||||
isLittleEndian: false});
|
||||
const component = createSignalLegendEntry({signal});
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
test("a big endian signal spanning 3 bytes on one byte should adjust its startBit switching to little endian, preserving its bit coverage", () => {
|
||||
const signal = new Signal({
|
||||
name: "signal",
|
||||
startBit: 15,
|
||||
size: 3,
|
||||
isLittleEndian: false
|
||||
});
|
||||
const component = createSignalLegendEntry({ signal });
|
||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||
"isLittleEndian"
|
||||
);
|
||||
component.instance().updateField(endiannessFieldSpec, true);
|
||||
|
||||
const signalEdited = component.state('signalEdited');
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(13);
|
||||
expect(signalEdited.size).toBe(3);
|
||||
});
|
||||
const signalEdited = component.state("signalEdited");
|
||||
expect(signalEdited.isLittleEndian).toBe(true);
|
||||
expect(signalEdited.startBit).toBe(13);
|
||||
expect(signalEdited.size).toBe(3);
|
||||
});
|
||||
|
|
|
@ -1,38 +1,45 @@
|
|||
// appendNewGraphData(plottedSignals, graphData, messages) {
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import GraphData from '../../models/graph-data';
|
||||
import Signal from '../../models/can/signal';
|
||||
import DBC from '../../models/can/dbc';
|
||||
import DbcUtils from '../../utils/dbc';
|
||||
import GraphData from "../../models/graph-data";
|
||||
import Signal from "../../models/can/signal";
|
||||
import DBC from "../../models/can/dbc";
|
||||
import DbcUtils from "../../utils/dbc";
|
||||
|
||||
function appendMockGraphData(existingGraphData, entryCount = 1) {
|
||||
const dbc = new DBC();
|
||||
const signal = new Signal({name: 'NEW_SIGNAL_1'});
|
||||
dbc.setSignals(0, {[signal.name]: signal});
|
||||
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 0);
|
||||
// time, relTime, data, byteStateChangeTimes) {
|
||||
message.entries = Array(entryCount).fill(DbcUtils.createMessageEntry(dbc, 0, 0, 0, Buffer.alloc(8), []));
|
||||
const messages = {[message.id]: message};
|
||||
const dbc = new DBC();
|
||||
const signal = new Signal({ name: "NEW_SIGNAL_1" });
|
||||
dbc.setSignals(0, { [signal.name]: signal });
|
||||
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 0);
|
||||
// time, relTime, data, byteStateChangeTimes) {
|
||||
message.entries = Array(entryCount).fill(
|
||||
DbcUtils.createMessageEntry(dbc, 0, 0, 0, Buffer.alloc(8), [])
|
||||
);
|
||||
const messages = { [message.id]: message };
|
||||
|
||||
const plottedSignals = [[{signalUid: signal.uid, messageId: '0'}]];
|
||||
const plottedSignals = [[{ signalUid: signal.uid, messageId: "0" }]];
|
||||
|
||||
return GraphData.appendNewGraphData(plottedSignals, existingGraphData, messages, 0);
|
||||
return GraphData.appendNewGraphData(
|
||||
plottedSignals,
|
||||
existingGraphData,
|
||||
messages,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
test('GraphData.appendNewGraphData adds messages to empty GraphData array', () => {
|
||||
const graphData = appendMockGraphData([[]]);
|
||||
expect(graphData.length).toEqual(1); // 1 plot
|
||||
expect(graphData[0].length).toEqual(1); // 1 message entry
|
||||
expect(graphData[0][0].x).toEqual(0); // message entry X value corresponds to provided time in createMessageEntry
|
||||
test("GraphData.appendNewGraphData adds messages to empty GraphData array", () => {
|
||||
const graphData = appendMockGraphData([[]]);
|
||||
expect(graphData.length).toEqual(1); // 1 plot
|
||||
expect(graphData[0].length).toEqual(1); // 1 message entry
|
||||
expect(graphData[0][0].x).toEqual(0); // message entry X value corresponds to provided time in createMessageEntry
|
||||
});
|
||||
|
||||
test('GraphData.appendNewGraphData does not change graph data when entries are unchanged', () => {
|
||||
let graphData = [[]];
|
||||
for(let i = 0; i < 100; i++) {
|
||||
graphData = appendMockGraphData(graphData);
|
||||
}
|
||||
test("GraphData.appendNewGraphData does not change graph data when entries are unchanged", () => {
|
||||
let graphData = [[]];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
graphData = appendMockGraphData(graphData);
|
||||
}
|
||||
|
||||
expect(graphData.length).toEqual(1);
|
||||
expect(graphData[0].length).toEqual(1);
|
||||
});
|
||||
expect(graphData.length).toEqual(1);
|
||||
expect(graphData[0].length).toEqual(1);
|
||||
});
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import Panda from '../../api/panda';
|
||||
import Panda from "../../api/panda";
|
||||
|
||||
function arrayBufferFromHex(hex) {
|
||||
const buffer = Buffer.from(hex, 'hex');
|
||||
const arrayBuffer = new ArrayBuffer(buffer.length);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for(let i = 0; i < buffer.length; i++) {
|
||||
view[i] = buffer[i];
|
||||
}
|
||||
return arrayBuffer;
|
||||
const buffer = Buffer.from(hex, "hex");
|
||||
const arrayBuffer = new ArrayBuffer(buffer.length);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
view[i] = buffer[i];
|
||||
}
|
||||
return arrayBuffer;
|
||||
}
|
||||
|
||||
test('parseCanBuffer correctly parses a message', () => {
|
||||
const panda = new Panda();
|
||||
// 16 byte buffer
|
||||
test("parseCanBuffer correctly parses a message", () => {
|
||||
const panda = new Panda();
|
||||
// 16 byte buffer
|
||||
|
||||
const arrayBuffer = arrayBufferFromHex('abababababababababababababababab');
|
||||
const arrayBuffer = arrayBufferFromHex("abababababababababababababababab");
|
||||
|
||||
const messages = panda.parseCanBuffer(arrayBuffer);
|
||||
expect(messages.length).toEqual(1)
|
||||
expect(messages[0]).toEqual([1373, 43947, 'abababababababab', 10]);
|
||||
const messages = panda.parseCanBuffer(arrayBuffer);
|
||||
expect(messages.length).toEqual(1);
|
||||
expect(messages[0]).toEqual([1373, 43947, "abababababababab", 10]);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const ACURA_DBC= `VERSION ""
|
||||
export const ACURA_DBC = `VERSION ""
|
||||
|
||||
|
||||
NS_ :
|
||||
|
@ -318,4 +318,4 @@ VAL_ 506 CHIME 4 "double_chime" 3 "single_chime" 2 "continuous_chime" 1 "repeati
|
|||
VAL_ 506 FCW 3 "fcw" 2 "fcw" 1 "fcw" 0 "no_fcw" ;
|
||||
VAL_ 780 HUD_LEAD 3 "no_car" 2 "solid_car" 1 "dashed_car" 0 "no_car" ;
|
||||
VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ;
|
||||
`;
|
||||
`;
|
||||
|
|
|
@ -404,4 +404,4 @@ VAL_ 904 MCU_clusterReadyForDrive 0 "NO_SNA" 1 "YES" ;
|
|||
VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
|
||||
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
|
||||
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
|
||||
`;
|
||||
`;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {shade} from '../../utils/color';
|
||||
import { shade } from "../../utils/color";
|
||||
|
||||
test('Shade darkens rgb white (255,255,255)', () => {
|
||||
const rgb = [255,255,255];
|
||||
const darkenRgb = shade(rgb, -0.5);
|
||||
test("Shade darkens rgb white (255,255,255)", () => {
|
||||
const rgb = [255, 255, 255];
|
||||
const darkenRgb = shade(rgb, -0.5);
|
||||
|
||||
expect(darkenRgb).toEqual([128,128,128]);
|
||||
});
|
||||
expect(darkenRgb).toEqual([128, 128, 128]);
|
||||
});
|
||||
|
|
|
@ -1,63 +1,70 @@
|
|||
global.__JEST__ = 1
|
||||
global.__JEST__ = 1;
|
||||
|
||||
import DbcUtils from '../../utils/dbc';
|
||||
import DBC from '../../models/can/dbc';
|
||||
import Signal from '../../models/can/signal';
|
||||
import DbcUtils from "../../utils/dbc";
|
||||
import DBC from "../../models/can/dbc";
|
||||
import Signal from "../../models/can/signal";
|
||||
|
||||
// want to mock pandareader and test processStreamedCanMessages
|
||||
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from('abababababababab', 'hex'), 1];
|
||||
const SAMPLE_MESSAGE_ID = '1:10';
|
||||
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from("abababababababab", "hex"), 1];
|
||||
const SAMPLE_MESSAGE_ID = "1:10";
|
||||
|
||||
function expectSampleMessageFieldsPreserved(messages, frame) {
|
||||
const [address, busTime, data, source] = SAMPLE_MESSAGE;
|
||||
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(Array(8).fill(0));
|
||||
const [address, busTime, data, source] = SAMPLE_MESSAGE;
|
||||
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(
|
||||
Array(8).fill(0)
|
||||
);
|
||||
}
|
||||
|
||||
// function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
|
||||
function addMessages(messages, message, dbc, n) {
|
||||
const firstCanTime = 0;
|
||||
message = [...message];
|
||||
let nextMessage = () => {message[1] = message[1] + 1; return message};
|
||||
const firstCanTime = 0;
|
||||
message = [...message];
|
||||
let nextMessage = () => {
|
||||
message[1] = message[1] + 1;
|
||||
return message;
|
||||
};
|
||||
|
||||
for(let i = 0; i < n; i++) {
|
||||
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
|
||||
}
|
||||
for (let i = 0; i < n; i++) {
|
||||
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
|
||||
}
|
||||
}
|
||||
test('addCanMessage should add raw can message with empty dbc', () => {
|
||||
const messages = {};
|
||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
|
||||
test("addCanMessage should add raw can message with empty dbc", () => {
|
||||
const messages = {};
|
||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
|
||||
|
||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
|
||||
expectSampleMessageFieldsPreserved(messages);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
|
||||
expectSampleMessageFieldsPreserved(messages);
|
||||
});
|
||||
|
||||
test('addCanMessage should add multiple raw can messages with empty dbc', () => {
|
||||
const messages = {};
|
||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
|
||||
test("addCanMessage should add multiple raw can messages with empty dbc", () => {
|
||||
const messages = {};
|
||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
|
||||
|
||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
|
||||
expectSampleMessageFieldsPreserved(messages);
|
||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
|
||||
expectSampleMessageFieldsPreserved(messages);
|
||||
});
|
||||
|
||||
test('addCanMessage should add parsed can message with dbc containing message spec', () => {
|
||||
const messages = {};
|
||||
// create dbc with message spec and signal for sample_message
|
||||
const dbc = new DBC();
|
||||
dbc.createFrame(SAMPLE_MESSAGE[0]);
|
||||
const signal = new Signal({name: 'NEW_SIGNAL', startBit: 0, size: 8});
|
||||
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
|
||||
test("addCanMessage should add parsed can message with dbc containing message spec", () => {
|
||||
const messages = {};
|
||||
// create dbc with message spec and signal for sample_message
|
||||
const dbc = new DBC();
|
||||
dbc.createFrame(SAMPLE_MESSAGE[0]);
|
||||
const signal = new Signal({ name: "NEW_SIGNAL", startBit: 0, size: 8 });
|
||||
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
|
||||
|
||||
// add 1 sample_message
|
||||
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
|
||||
// add 1 sample_message
|
||||
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
|
||||
|
||||
// verify message and parsed signal added
|
||||
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
|
||||
expect(sampleMessages.entries.length).toEqual(1);
|
||||
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
|
||||
expectSampleMessageFieldsPreserved(messages, dbc.messages.get(SAMPLE_MESSAGE[0]));
|
||||
// verify message and parsed signal added
|
||||
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
|
||||
expect(sampleMessages.entries.length).toEqual(1);
|
||||
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
|
||||
expectSampleMessageFieldsPreserved(
|
||||
messages,
|
||||
dbc.messages.get(SAMPLE_MESSAGE[0])
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import DBC from './models/can/dbc';
|
||||
import DBC from "./models/can/dbc";
|
||||
|
||||
const AcuraDbc = new DBC(`
|
||||
VERSION ""
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import GitHub from 'github-api';
|
||||
import GitHub from "github-api";
|
||||
|
||||
import {OPENDBC_SOURCE_REPO} from '../config';
|
||||
import { OPENDBC_SOURCE_REPO } from "../config";
|
||||
|
||||
export default class OpenDBC {
|
||||
constructor(token) {
|
||||
this.token = token;
|
||||
this.github = new GitHub({token});
|
||||
this.sourceRepo = this.github.getRepo('commaai', 'opendbc');
|
||||
this.github = new GitHub({ token });
|
||||
this.sourceRepo = this.github.getRepo("commaai", "opendbc");
|
||||
this.githubUsername = null;
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,11 @@ export default class OpenDBC {
|
|||
}
|
||||
|
||||
async getGithubUsername() {
|
||||
if(this.githubUsername) {
|
||||
if (this.githubUsername) {
|
||||
return this.githubUsername;
|
||||
} else {
|
||||
const githubUsername = await this.fetchGithubUsername();
|
||||
if(githubUsername) {
|
||||
if (githubUsername) {
|
||||
return githubUsername;
|
||||
}
|
||||
}
|
||||
|
@ -28,13 +28,13 @@ export default class OpenDBC {
|
|||
async fetchGithubUsername() {
|
||||
try {
|
||||
const user = await this.github.getUser();
|
||||
if(user) {
|
||||
if (user) {
|
||||
const profile = await user.getProfile();
|
||||
if(profile) {
|
||||
if (profile) {
|
||||
return profile.data.login;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -47,31 +47,31 @@ export default class OpenDBC {
|
|||
*/
|
||||
|
||||
let repo;
|
||||
if(repoFullName === undefined) {
|
||||
if (repoFullName === undefined) {
|
||||
repo = this.sourceRepo;
|
||||
} else {
|
||||
const [username, repoName] = repoFullName.split('/');
|
||||
const [username, repoName] = repoFullName.split("/");
|
||||
repo = this.github.getRepo(username, repoName);
|
||||
}
|
||||
try {
|
||||
const response = await repo.getContents('master', '');
|
||||
const response = await repo.getContents("master", "");
|
||||
|
||||
return response.data.map((content) => content.path);
|
||||
} catch(e) {
|
||||
return response.data.map(content => content.path);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getDbcContents(dbcPath, repoFullName) {
|
||||
let repo;
|
||||
if(repoFullName === undefined) {
|
||||
if (repoFullName === undefined) {
|
||||
repo = this.sourceRepo;
|
||||
} else {
|
||||
const [username, repoName] = repoFullName.split('/');
|
||||
const [username, repoName] = repoFullName.split("/");
|
||||
repo = this.github.getRepo(username, repoName);
|
||||
}
|
||||
|
||||
const fileContents = await repo.getContents('master', dbcPath);
|
||||
const fileContents = await repo.getContents("master", dbcPath);
|
||||
|
||||
const rawContentsUrl = fileContents.data.download_url;
|
||||
|
||||
|
@ -81,72 +81,83 @@ export default class OpenDBC {
|
|||
}
|
||||
|
||||
repoSourceIsOpenDbc(repoDetails) {
|
||||
return repoDetails.source
|
||||
&& repoDetails.source.full_name === OPENDBC_SOURCE_REPO;
|
||||
return (
|
||||
repoDetails.source && repoDetails.source.full_name === OPENDBC_SOURCE_REPO
|
||||
);
|
||||
}
|
||||
|
||||
async getUserOpenDbcFork() {
|
||||
const githubUsername = await this.getGithubUsername();
|
||||
if(!githubUsername) return null;
|
||||
const githubUsername = await this.getGithubUsername();
|
||||
if (!githubUsername) return null;
|
||||
|
||||
const openDbcFork = this.github.getRepo(githubUsername, 'opendbc');
|
||||
const repoDetailResp = await openDbcFork.getDetails();
|
||||
const repoDetails = repoDetailResp.data;
|
||||
const openDbcFork = this.github.getRepo(githubUsername, "opendbc");
|
||||
const repoDetailResp = await openDbcFork.getDetails();
|
||||
const repoDetails = repoDetailResp.data;
|
||||
|
||||
if(this.repoSourceIsOpenDbc(repoDetails)) {
|
||||
return repoDetails.full_name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (this.repoSourceIsOpenDbc(repoDetails)) {
|
||||
return repoDetails.full_name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async fork() {
|
||||
const forkResponse = await this.sourceRepo.fork();
|
||||
if(forkResponse.status === 202) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const forkResponse = await this.sourceRepo.fork();
|
||||
if (forkResponse.status === 202) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async commitFile(repoFullName, path, contents) {
|
||||
/*
|
||||
/*
|
||||
repo is of format username/reponame
|
||||
authenciated user must have write access to repo
|
||||
*/
|
||||
const [user, repoName] = repoFullName.split('/');
|
||||
const repo = this.github.getRepo(user, repoName);
|
||||
const [user, repoName] = repoFullName.split("/");
|
||||
const repo = this.github.getRepo(user, repoName);
|
||||
|
||||
// get HEAD reference
|
||||
const refResp = await repo.getRef('heads/master');
|
||||
const ref = refResp.data;
|
||||
// get HEAD reference
|
||||
const refResp = await repo.getRef("heads/master");
|
||||
const ref = refResp.data;
|
||||
|
||||
// get HEAD commit sha
|
||||
const headCommitResp = await repo.getCommit(ref.object.sha);
|
||||
const headCommit = headCommitResp.data;
|
||||
// get HEAD commit sha
|
||||
const headCommitResp = await repo.getCommit(ref.object.sha);
|
||||
const headCommit = headCommitResp.data;
|
||||
|
||||
// get HEAD tree
|
||||
const headTreeResp = await repo.getTree(headCommit.tree.sha);
|
||||
const headTree = headTreeResp.data;
|
||||
// get HEAD tree
|
||||
const headTreeResp = await repo.getTree(headCommit.tree.sha);
|
||||
const headTree = headTreeResp.data;
|
||||
|
||||
// create new tree
|
||||
const tree = [{
|
||||
mode: '100644',
|
||||
path: path,
|
||||
type: 'blob',
|
||||
content: contents,
|
||||
}];
|
||||
// create new tree
|
||||
const tree = [
|
||||
{
|
||||
mode: "100644",
|
||||
path: path,
|
||||
type: "blob",
|
||||
content: contents
|
||||
}
|
||||
];
|
||||
|
||||
const createTreeResp = await repo.createTree(tree, headTree.sha);
|
||||
const createdTree = createTreeResp.data;
|
||||
const createTreeResp = await repo.createTree(tree, headTree.sha);
|
||||
const createdTree = createTreeResp.data;
|
||||
|
||||
// commit
|
||||
const commitResp = await repo.commit(headCommit.sha, createdTree.sha, 'OpenDBC updates');
|
||||
const commit = commitResp.data;
|
||||
// commit
|
||||
const commitResp = await repo.commit(
|
||||
headCommit.sha,
|
||||
createdTree.sha,
|
||||
"OpenDBC updates"
|
||||
);
|
||||
const commit = commitResp.data;
|
||||
|
||||
// update HEAD
|
||||
const updateHeadResp = await repo.updateHead('heads/master', commit.sha, false);
|
||||
// update HEAD
|
||||
const updateHeadResp = await repo.updateHead(
|
||||
"heads/master",
|
||||
commit.sha,
|
||||
false
|
||||
);
|
||||
|
||||
return updateHeadResp.status === 200;
|
||||
return updateHeadResp.status === 200;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
import NumpyLoader from '../utils/loadnpy';
|
||||
import NumpyLoader from "../utils/loadnpy";
|
||||
|
||||
export async function fetchCanTimes(base, part) {
|
||||
const url = base+"/Log/"+part+"/can/t";
|
||||
const url = base + "/Log/" + part + "/can/t";
|
||||
|
||||
const canData = await NumpyLoader.promise(url);
|
||||
const canData = await NumpyLoader.promise(url);
|
||||
|
||||
return canData.data;
|
||||
return canData.data;
|
||||
}
|
||||
|
||||
export async function fetchCanPart(base, part) {
|
||||
var urls = [ base+"/Log/"+part+"/can/t",
|
||||
base+"/Log/"+part+"/can/src",
|
||||
base+"/Log/"+part+"/can/address",
|
||||
base+"/Log/"+part+"/can/data"];
|
||||
var urls = [
|
||||
base + "/Log/" + part + "/can/t",
|
||||
base + "/Log/" + part + "/can/src",
|
||||
base + "/Log/" + part + "/can/address",
|
||||
base + "/Log/" + part + "/can/data"
|
||||
];
|
||||
|
||||
var messages = {};
|
||||
let canData = null;
|
||||
try {
|
||||
canData = await Promise.all(urls.map(NumpyLoader.promise));
|
||||
} catch (e) {
|
||||
console.log('this is a 404 workaround that is hacky', e)
|
||||
return {times: [],
|
||||
sources: [],
|
||||
addresses: [],
|
||||
datas: []}
|
||||
}
|
||||
return {times: canData[0].data,
|
||||
sources: canData[1].data,
|
||||
addresses: canData[2].data,
|
||||
datas: canData[3].data};
|
||||
}
|
||||
var messages = {};
|
||||
let canData = null;
|
||||
try {
|
||||
canData = await Promise.all(urls.map(NumpyLoader.promise));
|
||||
} catch (e) {
|
||||
console.log("this is a 404 workaround that is hacky", e);
|
||||
return {
|
||||
times: [],
|
||||
sources: [],
|
||||
addresses: [],
|
||||
datas: []
|
||||
};
|
||||
}
|
||||
return {
|
||||
times: canData[0].data,
|
||||
sources: canData[1].data,
|
||||
addresses: canData[2].data,
|
||||
datas: canData[3].data
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import Cookies from 'js-cookie';
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
import {COMMA_ACCESS_TOKEN_COOKIE, COMMA_OAUTH_REDIRECT_COOKIE} from '../config';
|
||||
import {
|
||||
COMMA_ACCESS_TOKEN_COOKIE,
|
||||
COMMA_OAUTH_REDIRECT_COOKIE
|
||||
} from "../config";
|
||||
|
||||
function getCommaAccessToken() {
|
||||
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
|
||||
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
|
||||
}
|
||||
|
||||
function isAuthenticated() {
|
||||
return getCommaAccessToken() !== undefined;
|
||||
return getCommaAccessToken() !== undefined;
|
||||
}
|
||||
|
||||
function authUrl() {
|
||||
Cookies.set(COMMA_OAUTH_REDIRECT_COOKIE, window.location.href);
|
||||
Cookies.set(COMMA_OAUTH_REDIRECT_COOKIE, window.location.href);
|
||||
|
||||
return 'https://community.comma.ai/ucp.php?mode=login&login=external&oauth_service=google';
|
||||
return "https://community.comma.ai/ucp.php?mode=login&login=external&oauth_service=google";
|
||||
}
|
||||
|
||||
export default {getCommaAccessToken, isAuthenticated, authUrl};
|
||||
export default { getCommaAccessToken, isAuthenticated, authUrl };
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {GITHUB_CLIENT_ID, GITHUB_REDIRECT_URL} from '../config';
|
||||
import {objToQuery} from '../utils/url';
|
||||
import { GITHUB_CLIENT_ID, GITHUB_REDIRECT_URL } from "../config";
|
||||
import { objToQuery } from "../utils/url";
|
||||
|
||||
export function authorizeUrl(route) {
|
||||
const params = {client_id: GITHUB_CLIENT_ID,
|
||||
redirect_uri: GITHUB_REDIRECT_URL,
|
||||
scope: 'user:email public_repo',
|
||||
state: JSON.stringify({route})};
|
||||
const params = {
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
redirect_uri: GITHUB_REDIRECT_URL,
|
||||
scope: "user:email public_repo",
|
||||
state: JSON.stringify({ route })
|
||||
};
|
||||
|
||||
return `http://github.com/login/oauth/authorize?${objToQuery(params)}`;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
import DBC from '../models/can/dbc';
|
||||
import DBC from "../models/can/dbc";
|
||||
|
||||
export function fetchPersistedDbc(routeName) {
|
||||
const maybeDbc = window.localStorage.getItem(routeName);
|
||||
if(maybeDbc !== null) {
|
||||
const {dbcFilename, dbcText} = JSON.parse(maybeDbc);
|
||||
if (maybeDbc !== null) {
|
||||
const { dbcFilename, dbcText } = JSON.parse(maybeDbc);
|
||||
const dbc = new DBC(dbcText);
|
||||
|
||||
return {dbc, dbcText, dbcFilename};
|
||||
return { dbc, dbcText, dbcFilename };
|
||||
} else return null;
|
||||
}
|
||||
|
||||
export function persistDbc(routeName, {dbcFilename, dbc}) {
|
||||
const dbcJson = JSON.stringify({dbcFilename,
|
||||
dbcText: dbc.text()});
|
||||
export function persistDbc(routeName, { dbcFilename, dbc }) {
|
||||
const dbcJson = JSON.stringify({
|
||||
dbcFilename,
|
||||
dbcText: dbc.text()
|
||||
});
|
||||
window.localStorage.setItem(routeName, dbcJson);
|
||||
}
|
||||
|
||||
const GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY = 'gh_auth_token';
|
||||
const GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY = "gh_auth_token";
|
||||
export function fetchPersistedGithubAuthToken() {
|
||||
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
|
||||
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
|
||||
}
|
||||
|
||||
export function unpersistGithubAuthToken() {
|
||||
|
@ -26,5 +28,5 @@ export function unpersistGithubAuthToken() {
|
|||
}
|
||||
|
||||
export function persistGithubAuthToken(token) {
|
||||
return window.localStorage.setItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY, token);
|
||||
}
|
||||
return window.localStorage.setItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY, token);
|
||||
}
|
||||
|
|
|
@ -1,66 +1,76 @@
|
|||
import Panda from './panda';
|
||||
import Panda from "./panda";
|
||||
|
||||
export default class PandaReader {
|
||||
static ERROR_NO_DEVICE_SELECTED = 8;
|
||||
|
||||
static ERROR_NO_DEVICE_SELECTED = 8;
|
||||
constructor() {
|
||||
this.panda = new Panda();
|
||||
this.isReading = false;
|
||||
this.onMessagesReceived = () => {};
|
||||
this.callbackQueue = [];
|
||||
this.callbackQueueTimer = null;
|
||||
|
||||
constructor() {
|
||||
this.panda = new Panda();
|
||||
this.isReading = false;
|
||||
this.onMessagesReceived = () => {};
|
||||
this.callbackQueue = [];
|
||||
this.callbackQueueTimer = null;
|
||||
this.readLoop = this.readLoop.bind(this);
|
||||
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
|
||||
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
|
||||
}
|
||||
|
||||
this.readLoop = this.readLoop.bind(this);
|
||||
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
|
||||
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
|
||||
connect() {
|
||||
return this.panda.connect();
|
||||
}
|
||||
|
||||
setOnMessagesReceivedCallback(callback) {
|
||||
this.onMessagesReceived = callback;
|
||||
}
|
||||
|
||||
stopReadLoop() {
|
||||
this.isReading = false;
|
||||
window.cancelAnimationFrame(this.callbackQueueTimer);
|
||||
}
|
||||
|
||||
_flushCallbackQueue() {
|
||||
const messages = this.callbackQueue.reduce(
|
||||
(arr, messages) => arr.concat(messages),
|
||||
[]
|
||||
);
|
||||
this.onMessagesReceived(messages);
|
||||
|
||||
this.callbackQueue = [];
|
||||
}
|
||||
|
||||
flushCallbackQueue() {
|
||||
if (this.callbackQueue.length > 0) {
|
||||
this._flushCallbackQueue();
|
||||
}
|
||||
|
||||
connect() {
|
||||
return this.panda.connect();
|
||||
this.callbackQueueTimer = window.requestAnimationFrame(
|
||||
this.flushCallbackQueue
|
||||
);
|
||||
}
|
||||
|
||||
readLoop() {
|
||||
if (!this.isReading) {
|
||||
this.isReading = true;
|
||||
// this.flushCallbackQueueTimer = wi
|
||||
this.callbackQueueTimer = window.requestAnimationFrame(
|
||||
this.flushCallbackQueue,
|
||||
30
|
||||
);
|
||||
}
|
||||
|
||||
setOnMessagesReceivedCallback(callback) {
|
||||
this.onMessagesReceived = callback;
|
||||
}
|
||||
|
||||
stopReadLoop() {
|
||||
this.isReading = false;
|
||||
window.cancelAnimationFrame(this.callbackQueueTimer);
|
||||
}
|
||||
|
||||
_flushCallbackQueue() {
|
||||
const messages = this.callbackQueue.reduce((arr, messages) => arr.concat(messages), [])
|
||||
this.onMessagesReceived(messages);
|
||||
|
||||
this.callbackQueue = [];
|
||||
}
|
||||
|
||||
flushCallbackQueue() {
|
||||
if(this.callbackQueue.length > 0) {
|
||||
this._flushCallbackQueue();
|
||||
}
|
||||
|
||||
this.callbackQueueTimer = window.requestAnimationFrame(this.flushCallbackQueue);
|
||||
}
|
||||
|
||||
readLoop() {
|
||||
if(!this.isReading) {
|
||||
this.isReading = true;
|
||||
// this.flushCallbackQueueTimer = wi
|
||||
this.callbackQueueTimer = window.requestAnimationFrame(this.flushCallbackQueue, 30);
|
||||
this.panda.canRecv().then(
|
||||
messages => {
|
||||
if (this.isReading && messages.canMessages.length > 0) {
|
||||
this.callbackQueue.push(messages);
|
||||
}
|
||||
|
||||
this.panda.canRecv().then(messages => {
|
||||
if(this.isReading && messages.canMessages.length > 0) {
|
||||
this.callbackQueue.push(messages);
|
||||
}
|
||||
this.readLoop();
|
||||
},
|
||||
error => {
|
||||
if (this.isReading) {
|
||||
console.log("canRecv error", error);
|
||||
this.readLoop();
|
||||
}, error => {
|
||||
if(this.isReading) {
|
||||
console.log('canRecv error', error);
|
||||
this.readLoop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
154
src/api/panda.js
154
src/api/panda.js
|
@ -1,5 +1,5 @@
|
|||
import CloudLog from '../logging/CloudLog';
|
||||
require('core-js/fn/string/pad-end');
|
||||
import CloudLog from "../logging/CloudLog";
|
||||
require("core-js/fn/string/pad-end");
|
||||
|
||||
const PANDA_VENDOR_ID = 0xbbaa;
|
||||
const PANDA_PRODUCT_ID = 0xddcc;
|
||||
|
@ -7,73 +7,93 @@ const PANDA_PRODUCT_ID = 0xddcc;
|
|||
const BUFFER_SIZE = 0x10 * 256;
|
||||
|
||||
export default class Panda {
|
||||
constructor() {
|
||||
this.device = null;
|
||||
constructor() {
|
||||
this.device = null;
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Must be called via a mouse click handler, per Chrome restrictions.
|
||||
return navigator.usb
|
||||
.requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] })
|
||||
.then(device => {
|
||||
this.device = device;
|
||||
return device.open();
|
||||
})
|
||||
.then(() => this.device.selectConfiguration(1))
|
||||
.then(() => this.device.claimInterface(0));
|
||||
}
|
||||
|
||||
async health() {
|
||||
const controlParams = {
|
||||
requestType: "vendor",
|
||||
recipient: "device",
|
||||
request: 0xd2,
|
||||
value: 0,
|
||||
index: 0
|
||||
};
|
||||
try {
|
||||
return await this.device.controlTransferIn(controlParams, 13);
|
||||
} catch (err) {
|
||||
CloudLog.error({ event: "Panda.health failed", error: err });
|
||||
}
|
||||
}
|
||||
|
||||
parseCanBuffer(buffer) {
|
||||
const messages = [];
|
||||
|
||||
for (let i = 0; i < buffer.byteLength; i += 0x10) {
|
||||
const dat = buffer.slice(i, i + 0x10);
|
||||
|
||||
const datView = Buffer.from(dat);
|
||||
const f1 = datView.readInt32LE(0),
|
||||
f2 = datView.readInt32LE(4);
|
||||
|
||||
const address = f1 >>> 21;
|
||||
|
||||
const busTime = f2 >>> 16;
|
||||
const data = new Buffer(dat.slice(8, 8 + (f2 & 0xf)));
|
||||
const source = (f2 >> 4) & 0xf & 0xff;
|
||||
|
||||
messages.push([
|
||||
address,
|
||||
busTime,
|
||||
data.toString("hex").padEnd(16, "0"),
|
||||
source
|
||||
]);
|
||||
}
|
||||
|
||||
connect() {
|
||||
// Must be called via a mouse click handler, per Chrome restrictions.
|
||||
return navigator.usb.requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] })
|
||||
.then(device => {
|
||||
this.device = device;
|
||||
return device.open();
|
||||
})
|
||||
.then(() => this.device.selectConfiguration(1))
|
||||
.then(() => this.device.claimInterface(0));
|
||||
return messages;
|
||||
}
|
||||
|
||||
async mockCanRecv() {
|
||||
const promise = new Promise(resolve =>
|
||||
setTimeout(
|
||||
() =>
|
||||
resolve({
|
||||
time: performance.now() / 1000,
|
||||
canMessages: [[0, Math.random() * 65000, "".padEnd(16, "0"), 0]]
|
||||
}),
|
||||
100
|
||||
)
|
||||
);
|
||||
return await promise;
|
||||
}
|
||||
|
||||
async canRecv() {
|
||||
let result = null,
|
||||
receiptTime = null;
|
||||
while (result === null) {
|
||||
try {
|
||||
result = await this.device.transferIn(1, BUFFER_SIZE);
|
||||
receiptTime = performance.now() / 1000;
|
||||
} catch (err) {
|
||||
console.warn("can_recv failed, retrying");
|
||||
}
|
||||
}
|
||||
|
||||
async health() {
|
||||
const controlParams = { requestType: 'vendor',
|
||||
recipient: 'device',
|
||||
request: 0xd2,
|
||||
value: 0,
|
||||
index: 0 };
|
||||
try {
|
||||
return await this.device.controlTransferIn(controlParams, 13);
|
||||
} catch(err) {
|
||||
CloudLog.error({event: 'Panda.health failed', 'error': err});
|
||||
}
|
||||
}
|
||||
|
||||
parseCanBuffer(buffer) {
|
||||
const messages = [];
|
||||
|
||||
for(let i = 0; i < buffer.byteLength; i+=0x10) {
|
||||
const dat = buffer.slice(i, i + 0x10);
|
||||
|
||||
const datView = Buffer.from(dat);
|
||||
const f1 = datView.readInt32LE(0), f2 = datView.readInt32LE(4);
|
||||
|
||||
const address = f1 >>> 21;
|
||||
|
||||
const busTime = (f2 >>> 16);
|
||||
const data = new Buffer(dat.slice(8, 8 + (f2 & 0xF)));
|
||||
const source = ((f2 >> 4) & 0xF) & 0xFF;
|
||||
|
||||
messages.push([address, busTime, data.toString('hex').padEnd(16, '0'), source]);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
async mockCanRecv() {
|
||||
const promise = new Promise((resolve) =>
|
||||
setTimeout(() => resolve({time: performance.now() / 1000,
|
||||
canMessages: [[0, Math.random() * 65000, ''.padEnd(16, '0'), 0]]}), 100));
|
||||
return await promise;
|
||||
}
|
||||
|
||||
async canRecv() {
|
||||
let result = null, receiptTime = null;
|
||||
while(result === null) {
|
||||
try {
|
||||
result = await this.device.transferIn(1, BUFFER_SIZE);
|
||||
receiptTime = performance.now() / 1000;
|
||||
} catch(err) {
|
||||
console.warn('can_recv failed, retrying');
|
||||
}
|
||||
}
|
||||
|
||||
return {time: receiptTime, canMessages: this.parseCanBuffer(result.data.buffer)};
|
||||
}
|
||||
return {
|
||||
time: receiptTime,
|
||||
canMessages: this.parseCanBuffer(result.data.buffer)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import Cookies from 'js-cookie';
|
||||
import Moment from 'moment';
|
||||
import CommaAuth from './comma-auth';
|
||||
import Cookies from "js-cookie";
|
||||
import Moment from "moment";
|
||||
import CommaAuth from "./comma-auth";
|
||||
|
||||
const ROUTES_ENDPOINT = 'https://api.commadotai.com/v1/{dongleId}/routes/';
|
||||
const ROUTES_ENDPOINT = "https://api.commadotai.com/v1/{dongleId}/routes/";
|
||||
|
||||
function momentizeTimes(routes) {
|
||||
for(let routeName in routes) {
|
||||
for (let routeName in routes) {
|
||||
routes[routeName].start_time = Moment(routes[routeName].start_time);
|
||||
routes[routeName].end_time = Moment(routes[routeName].end_time);
|
||||
}
|
||||
|
@ -13,35 +13,34 @@ function momentizeTimes(routes) {
|
|||
}
|
||||
|
||||
export async function fetchRoutes(dongleId) {
|
||||
// will throw errors from fetch() on HTTP failure
|
||||
// will throw errors from fetch() on HTTP failure
|
||||
|
||||
if(dongleId === undefined) {
|
||||
dongleId = 'me';
|
||||
if (dongleId === undefined) {
|
||||
dongleId = "me";
|
||||
}
|
||||
|
||||
const accessToken = CommaAuth.getCommaAccessToken();
|
||||
if (accessToken) {
|
||||
const endpoint = ROUTES_ENDPOINT.replace("{dongleId}", dongleId);
|
||||
const headers = new Headers();
|
||||
headers.append("Authorization", `JWT ${accessToken}`);
|
||||
|
||||
const request = new Request(endpoint, { headers });
|
||||
const resp = await fetch(request);
|
||||
const routes = await resp.json();
|
||||
if ("routes" in routes) {
|
||||
return momentizeTimes(routes.routes);
|
||||
}
|
||||
}
|
||||
|
||||
const accessToken = CommaAuth.getCommaAccessToken();
|
||||
if(accessToken) {
|
||||
const endpoint = ROUTES_ENDPOINT.replace('{dongleId}', dongleId);
|
||||
const headers = new Headers();
|
||||
headers.append('Authorization', `JWT ${accessToken}`);
|
||||
|
||||
|
||||
const request = new Request(endpoint, {headers});
|
||||
const resp = await fetch(request);
|
||||
const routes = await resp.json();
|
||||
if('routes' in routes) {
|
||||
return momentizeTimes(routes.routes);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
export function cameraPath(routeUrl, frame) {
|
||||
return `${routeUrl}/sec${frame}.jpg`
|
||||
return `${routeUrl}/sec${frame}.jpg`;
|
||||
}
|
||||
|
||||
export function parseRouteName(name) {
|
||||
const startTime = Moment(name, "YYYY-MM-DD--H-m-s");
|
||||
return {startTime};
|
||||
return { startTime };
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import SocketIO from 'socket.io-client';
|
||||
import {UNLOGGER_HOST} from '../config';
|
||||
import SocketIO from "socket.io-client";
|
||||
import { UNLOGGER_HOST } from "../config";
|
||||
|
||||
export default class UnloggerClient {
|
||||
constructor() {
|
||||
this.socket = SocketIO(UNLOGGER_HOST);
|
||||
}
|
||||
constructor() {
|
||||
this.socket = SocketIO(UNLOGGER_HOST);
|
||||
}
|
||||
|
||||
seek(dongleId, baseTime, seekSeconds) {
|
||||
this.socket.emit('seek', dongleId, baseTime, seekSeconds);
|
||||
}
|
||||
}
|
||||
seek(dongleId, baseTime, seekSeconds) {
|
||||
this.socket.emit("seek", dongleId, baseTime, seekSeconds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,18 @@ function videoUrl(dongleId, hashedRouteName) {
|
|||
function videoUrlForRouteUrl(routeUrlString) {
|
||||
const url = new URL(routeUrlString);
|
||||
|
||||
const pathParts = url.pathname.split('/');
|
||||
const pathParts = url.pathname.split("/");
|
||||
|
||||
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(pathParts.length - 2);
|
||||
let dongleId = dongleIdPrefixed
|
||||
if(dongleIdPrefixed.indexOf('comma-') === 0) {
|
||||
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split('comma-');
|
||||
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(
|
||||
pathParts.length - 2
|
||||
);
|
||||
let dongleId = dongleIdPrefixed;
|
||||
if (dongleIdPrefixed.indexOf("comma-") === 0) {
|
||||
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split("comma-");
|
||||
dongleId = dongleIdNoPrefix;
|
||||
}
|
||||
|
||||
return videoUrl(dongleId, hashedRouteName);
|
||||
}
|
||||
|
||||
export default {videoUrl, videoUrlForRouteUrl};
|
||||
export default { videoUrl, videoUrlForRouteUrl };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import DBC from './models/can/dbc';
|
||||
import DBC from "./models/can/dbc";
|
||||
|
||||
const CivicDbc = new DBC(`
|
||||
VERSION ""
|
||||
|
@ -345,4 +345,4 @@ VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ;
|
|||
VAL_ 927 ACC_ALERTS 29 "esp_active_acc_canceled" 10 "b_pedal_applied" 9 "speed_too_low" 8 "speed_too_high" 7 "p_brake_applied" 6 "gear_no_d" 5 "seatbelt" 4 "too_steep_downhill" 3 "too_steep_uphill" 2 "too_close" 1 "no_vehicle_ahead" ;
|
||||
|
||||
`);
|
||||
export default CivicDbc;
|
||||
export default CivicDbc;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,268 +1,306 @@
|
|||
import React, {Component} from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import React, { Component } from "react";
|
||||
import Measure from "react-measure";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
|
||||
import Signal from '../models/can/signal';
|
||||
import CanPlot from '../vega/CanPlot';
|
||||
import Signal from "../models/can/signal";
|
||||
import CanPlot from "../vega/CanPlot";
|
||||
|
||||
const DefaultPlotInnerStyle = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
|
||||
export default class CanGraph extends Component {
|
||||
static emptyTable = [];
|
||||
static emptyTable = [];
|
||||
|
||||
static propTypes = {
|
||||
data: PropTypes.object,
|
||||
messages: PropTypes.object,
|
||||
messageId: PropTypes.string,
|
||||
messageName: PropTypes.string,
|
||||
signalSpec: PropTypes.instanceOf(Signal),
|
||||
segment: PropTypes.array,
|
||||
unplot: PropTypes.func,
|
||||
onRelativeTimeClick: PropTypes.func,
|
||||
currentTime: PropTypes.number,
|
||||
onSegmentChanged: PropTypes.func,
|
||||
onDragStart: PropTypes.func,
|
||||
onDragEnd: PropTypes.func,
|
||||
container: PropTypes.node,
|
||||
dragPos: PropTypes.object,
|
||||
canReceiveGraphDrop: PropTypes.bool,
|
||||
onGraphRefAvailable: PropTypes.func,
|
||||
plottedSignals: PropTypes.array,
|
||||
static propTypes = {
|
||||
data: PropTypes.object,
|
||||
messages: PropTypes.object,
|
||||
messageId: PropTypes.string,
|
||||
messageName: PropTypes.string,
|
||||
signalSpec: PropTypes.instanceOf(Signal),
|
||||
segment: PropTypes.array,
|
||||
unplot: PropTypes.func,
|
||||
onRelativeTimeClick: PropTypes.func,
|
||||
currentTime: PropTypes.number,
|
||||
onSegmentChanged: PropTypes.func,
|
||||
onDragStart: PropTypes.func,
|
||||
onDragEnd: PropTypes.func,
|
||||
container: PropTypes.node,
|
||||
dragPos: PropTypes.object,
|
||||
canReceiveGraphDrop: PropTypes.bool,
|
||||
onGraphRefAvailable: PropTypes.func,
|
||||
plottedSignals: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
plotInnerStyle: null,
|
||||
shiftX: 0,
|
||||
shiftY: 0,
|
||||
bounds: null,
|
||||
isDataInserted: false
|
||||
};
|
||||
this.onNewView = this.onNewView.bind(this);
|
||||
this.onSignalClickTime = this.onSignalClickTime.bind(this);
|
||||
this.onSignalSegment = this.onSignalSegment.bind(this);
|
||||
this.onDragAnchorMouseDown = this.onDragAnchorMouseDown.bind(this);
|
||||
this.onDragAnchorMouseUp = this.onDragAnchorMouseUp.bind(this);
|
||||
this.onDragStart = this.onDragStart.bind(this);
|
||||
this.onPlotResize = this.onPlotResize.bind(this);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
segmentIsNew(newSegment) {
|
||||
return (
|
||||
newSegment.length !== this.props.segment.length ||
|
||||
!newSegment.every((val, idx) => this.props.segment[idx] === val)
|
||||
);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
plotInnerStyle: null,
|
||||
shiftX: 0,
|
||||
shiftY: 0,
|
||||
bounds: null,
|
||||
isDataInserted: false,
|
||||
};
|
||||
this.onNewView = this.onNewView.bind(this);
|
||||
this.onSignalClickTime = this.onSignalClickTime.bind(this);
|
||||
this.onSignalSegment = this.onSignalSegment.bind(this);
|
||||
this.onDragAnchorMouseDown = this.onDragAnchorMouseDown.bind(this);
|
||||
this.onDragAnchorMouseUp = this.onDragAnchorMouseUp.bind(this);
|
||||
this.onDragStart = this.onDragStart.bind(this);
|
||||
this.onPlotResize = this.onPlotResize.bind(this);
|
||||
}
|
||||
dataChanged(prevProps, nextProps) {
|
||||
return (
|
||||
nextProps.data.series.length !== prevProps.data.series.length ||
|
||||
!prevProps.signalSpec.equals(nextProps.signalSpec) ||
|
||||
nextProps.data.updated !== this.props.data.updated
|
||||
);
|
||||
}
|
||||
|
||||
segmentIsNew(newSegment) {
|
||||
return newSegment.length !== this.props.segment.length
|
||||
|| !(newSegment.every((val, idx) => this.props.segment[idx] === val));
|
||||
}
|
||||
visualChanged(prevProps, nextProps) {
|
||||
return (
|
||||
prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop ||
|
||||
JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos)
|
||||
);
|
||||
}
|
||||
|
||||
dataChanged(prevProps, nextProps) {
|
||||
return nextProps.data.series.length !== prevProps.data.series.length
|
||||
|| !prevProps.signalSpec.equals(nextProps.signalSpec)
|
||||
|| nextProps.data.updated !== this.props.data.updated;
|
||||
}
|
||||
onPlotResize({ bounds }) {
|
||||
this.setState({ bounds });
|
||||
|
||||
visualChanged(prevProps, nextProps) {
|
||||
return prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop
|
||||
|| JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos);
|
||||
}
|
||||
this.view.run();
|
||||
this.view.signal("width", bounds.width);
|
||||
this.view.signal("height", 0.4 * bounds.width); // 5:2 aspect ratio
|
||||
this.view.run();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (this.view) {
|
||||
// only update if segment is new
|
||||
let segmentChanged = false;
|
||||
if (this.segmentIsNew(nextProps.segment)) {
|
||||
if (nextProps.segment.length > 0) {
|
||||
// Set segmented domain
|
||||
this.view.signal("segment", nextProps.segment);
|
||||
} else {
|
||||
// Reset segment to full domain
|
||||
this.view.signal("segment", 0);
|
||||
}
|
||||
segmentChanged = true;
|
||||
}
|
||||
|
||||
onPlotResize({bounds}) {
|
||||
this.setState({bounds});
|
||||
if (!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
|
||||
this.view.signal("videoTime", nextProps.currentTime);
|
||||
segmentChanged = true;
|
||||
}
|
||||
|
||||
if (segmentChanged) {
|
||||
this.view.run();
|
||||
this.view.signal('width', bounds.width);
|
||||
this.view.signal('height', 0.4 * bounds.width); // 5:2 aspect ratio
|
||||
this.view.run();
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if(this.view) {
|
||||
// only update if segment is new
|
||||
let segmentChanged = false;
|
||||
if(this.segmentIsNew(nextProps.segment)) {
|
||||
if(nextProps.segment.length > 0) {
|
||||
// Set segmented domain
|
||||
this.view.signal('segment', nextProps.segment)
|
||||
} else {
|
||||
// Reset segment to full domain
|
||||
this.view.signal('segment', 0);
|
||||
}
|
||||
segmentChanged = true;
|
||||
}
|
||||
const dataChanged = this.dataChanged(this.props, nextProps);
|
||||
|
||||
if(!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
|
||||
this.view.signal('videoTime', nextProps.currentTime);
|
||||
segmentChanged = true;
|
||||
}
|
||||
return (
|
||||
dataChanged ||
|
||||
JSON.stringify(this.state) !== JSON.stringify(nextState) ||
|
||||
this.visualChanged(this.props, nextProps)
|
||||
);
|
||||
}
|
||||
|
||||
if(segmentChanged) {
|
||||
this.view.run();
|
||||
}
|
||||
}
|
||||
insertData() {
|
||||
this.view.remove("table", () => true).run();
|
||||
this.view.insert("table", this.props.data.series).run();
|
||||
}
|
||||
|
||||
const dataChanged = this.dataChanged(this.props, nextProps);
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.dataChanged(prevProps, this.props)) {
|
||||
this.insertData();
|
||||
}
|
||||
}
|
||||
|
||||
return dataChanged
|
||||
|| JSON.stringify(this.state) !== JSON.stringify(nextState)
|
||||
|| this.visualChanged(this.props, nextProps);
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.dragPos &&
|
||||
JSON.stringify(nextProps.dragPos) !== JSON.stringify(this.props.dragPos)
|
||||
) {
|
||||
this.updateStyleFromDragPos(nextProps.dragPos);
|
||||
} else if (!nextProps.dragPos && this.state.plotInnerStyle !== null) {
|
||||
this.setState({ plotInnerStyle: null });
|
||||
}
|
||||
}
|
||||
|
||||
updateStyleFromDragPos({ left, top }) {
|
||||
const plotInnerStyle = this.state.plotInnerStyle || {};
|
||||
plotInnerStyle.left = left;
|
||||
plotInnerStyle.top = top;
|
||||
this.setState({ plotInnerStyle });
|
||||
}
|
||||
|
||||
onNewView(view) {
|
||||
this.view = view;
|
||||
if (this.state.bounds) {
|
||||
this.onPlotResize({ bounds: this.state.bounds });
|
||||
}
|
||||
if (this.props.segment.length > 0) {
|
||||
view.signal("segment", this.props.segment);
|
||||
}
|
||||
|
||||
insertData() {
|
||||
this.view.remove('table', () => true).run();
|
||||
this.view.insert('table', this.props.data.series).run();
|
||||
this.insertData();
|
||||
}
|
||||
|
||||
onSignalClickTime(signal, clickTime) {
|
||||
if (clickTime !== undefined) {
|
||||
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
|
||||
}
|
||||
}
|
||||
|
||||
onSignalSegment(signal, segment) {
|
||||
if (!Array.isArray(segment)) {
|
||||
return;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if(this.dataChanged(prevProps, this.props)) {
|
||||
this.insertData();
|
||||
}
|
||||
this.props.onSegmentChanged(this.props.messageId, segment);
|
||||
if (this.view) {
|
||||
const state = this.view.getState();
|
||||
state.subcontext[0].signals.brush = 0;
|
||||
this.view = this.view.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.dragPos && JSON.stringify(nextProps.dragPos) !== JSON.stringify(this.props.dragPos)) {
|
||||
this.updateStyleFromDragPos(nextProps.dragPos);
|
||||
} else if(!nextProps.dragPos && this.state.plotInnerStyle !== null) {
|
||||
this.setState({plotInnerStyle: null});
|
||||
}
|
||||
}
|
||||
plotInnerStyleFromMouseEvent(e) {
|
||||
const { shiftX, shiftY } = this.state;
|
||||
const plotInnerStyle = { ...DefaultPlotInnerStyle };
|
||||
const rect = this.props.container.getBoundingClientRect();
|
||||
|
||||
updateStyleFromDragPos({left, top}) {
|
||||
const plotInnerStyle = this.state.plotInnerStyle || {};
|
||||
plotInnerStyle.left = left;
|
||||
plotInnerStyle.top = top;
|
||||
this.setState({plotInnerStyle});
|
||||
}
|
||||
const x = e.clientX - rect.left - shiftX;
|
||||
const y = e.clientY - rect.top - shiftY;
|
||||
plotInnerStyle.left = x;
|
||||
plotInnerStyle.top = y;
|
||||
return plotInnerStyle;
|
||||
}
|
||||
|
||||
onNewView(view) {
|
||||
this.view = view;
|
||||
if(this.state.bounds) {
|
||||
this.onPlotResize({bounds: this.state.bounds});
|
||||
}
|
||||
if(this.props.segment.length > 0) {
|
||||
view.signal('segment', this.props.segment);
|
||||
}
|
||||
onDragAnchorMouseDown(e) {
|
||||
e.persist();
|
||||
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
|
||||
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
|
||||
this.setState({ shiftX, shiftY }, () => {
|
||||
this.setState({ plotInnerStyle: this.plotInnerStyleFromMouseEvent(e) });
|
||||
});
|
||||
this.props.onDragStart(
|
||||
this.props.messageId,
|
||||
this.props.signalSpec.uid,
|
||||
shiftX,
|
||||
shiftY
|
||||
);
|
||||
}
|
||||
|
||||
this.insertData();
|
||||
}
|
||||
onDragAnchorMouseUp(e) {
|
||||
this.props.onDragEnd();
|
||||
this.setState({
|
||||
plotInnerStyle: null,
|
||||
shiftX: 0,
|
||||
shiftY: 0
|
||||
});
|
||||
}
|
||||
|
||||
onSignalClickTime(signal, clickTime) {
|
||||
if(clickTime !== undefined) {
|
||||
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
|
||||
}
|
||||
}
|
||||
onDragStart(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
onSignalSegment(signal, segment) {
|
||||
if(!Array.isArray(segment)) {
|
||||
return;
|
||||
}
|
||||
render() {
|
||||
const { plotInnerStyle } = this.state;
|
||||
const canReceiveDropClass = this.props.canReceiveGraphDrop
|
||||
? "is-droppable"
|
||||
: null;
|
||||
|
||||
this.props.onSegmentChanged(this.props.messageId, segment);
|
||||
if(this.view) {
|
||||
const state = this.view.getState();
|
||||
state.subcontext[0].signals.brush = 0;
|
||||
this.view = this.view.setState(state);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="cabana-explorer-visuals-plot"
|
||||
ref={this.props.onGraphRefAvailable}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
"cabana-explorer-visuals-plot-inner",
|
||||
canReceiveDropClass
|
||||
)}
|
||||
style={plotInnerStyle || null}
|
||||
>
|
||||
<div
|
||||
className="cabana-explorer-visuals-plot-draganchor"
|
||||
onMouseDown={this.onDragAnchorMouseDown}
|
||||
>
|
||||
<span className="fa fa-bars" />
|
||||
</div>
|
||||
{this.props.plottedSignals.map(
|
||||
({ messageId, signalUid, messageName }) => {
|
||||
const signal = Object.values(
|
||||
this.props.messages[messageId].frame.signals
|
||||
).find(s => s.uid === signalUid);
|
||||
const { colors } = signal;
|
||||
|
||||
plotInnerStyleFromMouseEvent(e) {
|
||||
const {shiftX, shiftY} = this.state;
|
||||
const plotInnerStyle = {...DefaultPlotInnerStyle};
|
||||
const rect = this.props.container.getBoundingClientRect();
|
||||
|
||||
const x = e.clientX - rect.left - shiftX;
|
||||
const y = e.clientY - rect.top - shiftY;
|
||||
plotInnerStyle.left = x;
|
||||
plotInnerStyle.top = y;
|
||||
return plotInnerStyle;
|
||||
}
|
||||
|
||||
onDragAnchorMouseDown(e) {
|
||||
e.persist();
|
||||
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
|
||||
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
|
||||
this.setState({shiftX, shiftY},
|
||||
() => {
|
||||
this.setState({plotInnerStyle: this.plotInnerStyleFromMouseEvent(e)});
|
||||
});
|
||||
this.props.onDragStart(this.props.messageId, this.props.signalSpec.uid, shiftX, shiftY);
|
||||
}
|
||||
|
||||
onDragAnchorMouseUp(e) {
|
||||
this.props.onDragEnd();
|
||||
this.setState({plotInnerStyle: null,
|
||||
shiftX: 0,
|
||||
shiftY: 0});
|
||||
}
|
||||
|
||||
onDragStart(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {plotInnerStyle} = this.state;
|
||||
const canReceiveDropClass = this.props.canReceiveGraphDrop ? 'is-droppable' : null;
|
||||
|
||||
return (
|
||||
<div className='cabana-explorer-visuals-plot'
|
||||
ref={this.props.onGraphRefAvailable}>
|
||||
<div className={ cx('cabana-explorer-visuals-plot-inner', canReceiveDropClass) }
|
||||
style={ plotInnerStyle || null }>
|
||||
<div className='cabana-explorer-visuals-plot-draganchor'
|
||||
onMouseDown={this.onDragAnchorMouseDown}>
|
||||
<span className='fa fa-bars'></span>
|
||||
return (
|
||||
<div
|
||||
className="cabana-explorer-visuals-plot-header"
|
||||
key={messageId + "_" + signal.uid}
|
||||
>
|
||||
<div className="cabana-explorer-visuals-plot-header-toggle">
|
||||
<button
|
||||
className="button--tiny"
|
||||
onClick={() => this.props.unplot(messageId, signalUid)}
|
||||
>
|
||||
<span>Hide Plot</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="cabana-explorer-visuals-plot-header-copy">
|
||||
<div className="cabana-explorer-visuals-plot-message">
|
||||
<span>{messageName}</span>
|
||||
</div>
|
||||
{this.props.plottedSignals.map(({ messageId, signalUid, messageName }) => {
|
||||
const signal = Object.values(this.props.messages[messageId].frame.signals).find((s) => s.uid === signalUid);
|
||||
const { colors } = signal;
|
||||
|
||||
return (
|
||||
<div className='cabana-explorer-visuals-plot-header'
|
||||
key={messageId + '_' + signal.uid}>
|
||||
<div className='cabana-explorer-visuals-plot-header-toggle'>
|
||||
<button className='button--tiny'
|
||||
onClick={ () => this.props.unplot(messageId, signalUid)}>
|
||||
<span>Hide Plot</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className='cabana-explorer-visuals-plot-header-copy'>
|
||||
<div className='cabana-explorer-visuals-plot-message'>
|
||||
<span>{messageName}</span>
|
||||
</div>
|
||||
<div className='cabana-explorer-visuals-plot-signal'>
|
||||
<div className='cabana-explorer-visuals-plot-signal-color'
|
||||
style={{background: `rgb(${colors}`}}></div>
|
||||
<strong>{signal.name}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
<Measure
|
||||
bounds
|
||||
onResize={this.onPlotResize}>
|
||||
{({measureRef}) => {
|
||||
return (<div ref={measureRef}
|
||||
className='cabana-explorer-visuals-plot-container'>
|
||||
<CanPlot
|
||||
logLevel={0}
|
||||
data={{table: CanGraph.emptyTable}}
|
||||
onNewView={this.onNewView}
|
||||
onSignalClickTime={this.onSignalClickTime}
|
||||
onSignalSegment={this.onSignalSegment}
|
||||
renderer={'canvas'}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
</Measure>
|
||||
<div className="cabana-explorer-visuals-plot-signal">
|
||||
<div
|
||||
className="cabana-explorer-visuals-plot-signal-color"
|
||||
style={{ background: `rgb(${colors}` }}
|
||||
/>
|
||||
<strong>{signal.name}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
)}
|
||||
<Measure bounds onResize={this.onPlotResize}>
|
||||
{({ measureRef }) => {
|
||||
return (
|
||||
<div
|
||||
ref={measureRef}
|
||||
className="cabana-explorer-visuals-plot-container"
|
||||
>
|
||||
<CanPlot
|
||||
logLevel={0}
|
||||
data={{ table: CanGraph.emptyTable }}
|
||||
onNewView={this.onNewView}
|
||||
onSignalClickTime={this.onSignalClickTime}
|
||||
onSignalSegment={this.onSignalSegment}
|
||||
renderer={"canvas"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,268 +1,310 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactList from 'react-list';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactList from "react-list";
|
||||
|
||||
import cx from 'classnames';
|
||||
import cx from "classnames";
|
||||
|
||||
export default class CanLog extends Component {
|
||||
static ITEMS_PER_PAGE = 50;
|
||||
static ITEMS_PER_PAGE = 50;
|
||||
|
||||
static propTypes = {
|
||||
plottedSignals: PropTypes.array,
|
||||
segmentIndices: PropTypes.array,
|
||||
onSignalUnplotPressed: PropTypes.func,
|
||||
onSignalPlotPressed: PropTypes.func,
|
||||
message: PropTypes.object,
|
||||
messageIndex: PropTypes.number,
|
||||
onMessageExpanded: PropTypes.func,
|
||||
static propTypes = {
|
||||
plottedSignals: PropTypes.array,
|
||||
segmentIndices: PropTypes.array,
|
||||
onSignalUnplotPressed: PropTypes.func,
|
||||
onSignalPlotPressed: PropTypes.func,
|
||||
message: PropTypes.object,
|
||||
messageIndex: PropTypes.number,
|
||||
onMessageExpanded: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// only want to display up to length elements at a time
|
||||
// offset, length
|
||||
|
||||
this.state = {
|
||||
length: 0,
|
||||
expandedMessages: [],
|
||||
messageHeights: [],
|
||||
allPacketsExpanded: false
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// only want to display up to length elements at a time
|
||||
// offset, length
|
||||
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
|
||||
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
|
||||
this.renderLogListItem = this.renderLogListItem.bind(this);
|
||||
this.renderLogList = this.renderLogList.bind(this);
|
||||
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
|
||||
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
length: 0,
|
||||
expandedMessages: [],
|
||||
messageHeights: [],
|
||||
allPacketsExpanded: false
|
||||
}
|
||||
|
||||
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
|
||||
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
|
||||
this.renderLogListItem = this.renderLogListItem.bind(this);
|
||||
this.renderLogList = this.renderLogList.bind(this);
|
||||
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
|
||||
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.message && !this.props.message) {
|
||||
this.addDisplayedMessages();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.message && !this.props.message) {
|
||||
this.addDisplayedMessages();
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const curMessageLength = this.props.message
|
||||
? this.props.message.entries.length
|
||||
: 0;
|
||||
const nextMessageLength = nextProps.message
|
||||
? nextProps.message.entries.length
|
||||
: 0;
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const curMessageLength = this.props.message ? this.props.message.entries.length : 0;
|
||||
const nextMessageLength = nextProps.message ? nextProps.message.entries.length : 0;
|
||||
const shouldUpdate =
|
||||
this.props.message !== nextProps.message ||
|
||||
nextMessageLength !== curMessageLength ||
|
||||
nextProps.messageIndex !== this.props.messageIndex ||
|
||||
nextProps.plottedSignals.length !== this.props.plottedSignals.length ||
|
||||
JSON.stringify(nextProps.segmentIndices) !==
|
||||
JSON.stringify(this.props.segmentIndices) ||
|
||||
JSON.stringify(nextState) !== JSON.stringify(this.state) ||
|
||||
this.props.message !== nextProps.message ||
|
||||
(this.props.message !== undefined &&
|
||||
nextProps.message !== undefined &&
|
||||
this.props.message.frame !== undefined &&
|
||||
nextProps.message.frame !== undefined &&
|
||||
JSON.stringify(this.props.message.frame) !==
|
||||
JSON.stringify(nextProps.message.frame));
|
||||
|
||||
const shouldUpdate = this.props.message !== nextProps.message
|
||||
|| nextMessageLength !== curMessageLength
|
||||
|| nextProps.messageIndex !== this.props.messageIndex
|
||||
|| nextProps.plottedSignals.length !== this.props.plottedSignals.length
|
||||
|| JSON.stringify(nextProps.segmentIndices) !== JSON.stringify(this.props.segmentIndices)
|
||||
|| JSON.stringify(nextState) !== JSON.stringify(this.state)
|
||||
|| this.props.message !== nextProps.message
|
||||
|| (this.props.message !== undefined
|
||||
&& nextProps.message !== undefined
|
||||
&& this.props.message.frame !== undefined
|
||||
&& nextProps.message.frame !== undefined
|
||||
&&
|
||||
(
|
||||
(JSON.stringify(this.props.message.frame) !== JSON.stringify(nextProps.message.frame))
|
||||
));
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
return shouldUpdate;
|
||||
}
|
||||
addDisplayedMessages() {
|
||||
const { length } = this.state;
|
||||
const newLength = length + CanLog.ITEMS_PER_PAGE;
|
||||
|
||||
addDisplayedMessages() {
|
||||
const {length} = this.state;
|
||||
const newLength = length + CanLog.ITEMS_PER_PAGE;
|
||||
this.setState({ length: newLength });
|
||||
}
|
||||
|
||||
this.setState({length: newLength});
|
||||
}
|
||||
expandMessage(msg, msgIdx) {
|
||||
this.setState({
|
||||
expandedMessages: this.state.expandedMessages.concat([msg.time])
|
||||
});
|
||||
this.props.onMessageExpanded();
|
||||
}
|
||||
|
||||
expandMessage(msg, msgIdx) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
|
||||
this.props.onMessageExpanded();
|
||||
}
|
||||
|
||||
collapseMessage(msg, msgIdx) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages
|
||||
.filter((expMsgTime) => expMsgTime !== msg.time)})
|
||||
}
|
||||
|
||||
isSignalPlotted(msgId, signalUid) {
|
||||
const plottedSignal = this.props.plottedSignals.find((plot) =>
|
||||
plot.some((signal) => signal.messageId === msgId && signal.signalUid === signalUid));
|
||||
return plottedSignal !== undefined;
|
||||
}
|
||||
|
||||
signalValuePretty(signal, value) {
|
||||
if(signal.isFloat) {
|
||||
return value.toFixed(3);
|
||||
} else return value;
|
||||
}
|
||||
|
||||
isMessageExpanded(msg) {
|
||||
return this.state.expandedMessages.indexOf(msg.time) !== -1;
|
||||
}
|
||||
|
||||
toggleSignalPlot(msg, signalUid, plotted) {
|
||||
if (!plotted) {
|
||||
this.props.onSignalPlotPressed(msg, signalUid);
|
||||
} else {
|
||||
this.props.onSignalUnplotPressed(msg, signalUid);
|
||||
}
|
||||
}
|
||||
|
||||
toggleExpandPacketSignals(msgEntry) {
|
||||
if(!this.props.message.frame) {
|
||||
return;
|
||||
}
|
||||
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
||||
|
||||
const msgHasSignals = Object.keys(this.props.message.frame.signals).length > 0;
|
||||
if (msgIsExpanded && msgHasSignals) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages
|
||||
.filter((expMsgTime) => expMsgTime !== msgEntry.time)})
|
||||
} else if (msgHasSignals) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages.concat([msgEntry.time])})
|
||||
this.props.onMessageExpanded();
|
||||
} else { return; }
|
||||
}
|
||||
|
||||
renderLogListItemSignals(msgEntry) {
|
||||
const { message } = this.props;
|
||||
return (
|
||||
<div className='signals-log-list-signals'>
|
||||
{ Object.entries(msgEntry.signals).map(
|
||||
([name, value]) => {
|
||||
const signal = message.frame.signals[name];
|
||||
if (signal === undefined) {
|
||||
// Signal removed?
|
||||
return null;
|
||||
}
|
||||
const unit = signal.unit.length > 0 ? signal.unit : 'units';
|
||||
const isPlotted = this.isSignalPlotted(message.id, signal.uid);
|
||||
const plottedButtonClass = isPlotted ? null : 'button--alpha';
|
||||
const plottedButtonText = isPlotted ? 'Hide Plot' : 'Show Plot';
|
||||
return (
|
||||
<div key={ name } className='signals-log-list-signal'>
|
||||
<div className='signals-log-list-signal-message'>
|
||||
<span>{ name }</span>
|
||||
</div>
|
||||
<div className='signals-log-list-signal-value'>
|
||||
<span>
|
||||
(<strong>{ this.signalValuePretty(signal, value) }</strong> { unit })
|
||||
</span>
|
||||
</div>
|
||||
<div className='signals-log-list-signal-action'
|
||||
onClick={ () => { this.toggleSignalPlot(message.id, signal.uid, isPlotted) } }>
|
||||
<button className={ cx('button--tiny', plottedButtonClass) }>
|
||||
<span>{ plottedButtonText }</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
collapseMessage(msg, msgIdx) {
|
||||
this.setState({
|
||||
expandedMessages: this.state.expandedMessages.filter(
|
||||
expMsgTime => expMsgTime !== msg.time
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
isSignalPlotted(msgId, signalUid) {
|
||||
const plottedSignal = this.props.plottedSignals.find(plot =>
|
||||
plot.some(
|
||||
signal => signal.messageId === msgId && signal.signalUid === signalUid
|
||||
)
|
||||
);
|
||||
return plottedSignal !== undefined;
|
||||
}
|
||||
|
||||
signalValuePretty(signal, value) {
|
||||
if (signal.isFloat) {
|
||||
return value.toFixed(3);
|
||||
} else return value;
|
||||
}
|
||||
|
||||
isMessageExpanded(msg) {
|
||||
return this.state.expandedMessages.indexOf(msg.time) !== -1;
|
||||
}
|
||||
|
||||
toggleSignalPlot(msg, signalUid, plotted) {
|
||||
if (!plotted) {
|
||||
this.props.onSignalPlotPressed(msg, signalUid);
|
||||
} else {
|
||||
this.props.onSignalUnplotPressed(msg, signalUid);
|
||||
}
|
||||
}
|
||||
|
||||
renderLogListItemMessage(msgEntry, key) {
|
||||
const { message } = this.props;
|
||||
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
||||
const msgHasSignals = Object.keys(msgEntry.signals).length > 0;
|
||||
const hasSignalsClass = msgHasSignals ? 'has-signals' : null;
|
||||
const expandedClass = msgIsExpanded ? 'is-expanded' : null;
|
||||
const row = (
|
||||
<div key={key} className={cx('signals-log-list-item', hasSignalsClass, expandedClass)}>
|
||||
<div className='signals-log-list-item-header'
|
||||
onClick={ () => { this.toggleExpandPacketSignals(msgEntry) } }>
|
||||
<div className='signals-log-list-message'>
|
||||
<strong>{(message.frame ? message.frame.name : null) || message.id}</strong>
|
||||
</div>
|
||||
<div className='signals-log-list-time'>
|
||||
<span>[{msgEntry.relTime.toFixed(3)}]</span>
|
||||
</div>
|
||||
<div className='signals-log-list-bytes'>
|
||||
<span className='t-mono'>{msgEntry.hexData}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='signals-log-list-item-body'>
|
||||
{ msgIsExpanded ? this.renderLogListItemSignals(msgEntry) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return row;
|
||||
toggleExpandPacketSignals(msgEntry) {
|
||||
if (!this.props.message.frame) {
|
||||
return;
|
||||
}
|
||||
const msgIsExpanded =
|
||||
this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
||||
|
||||
renderLogListItem(index, key) {
|
||||
let offset = this.props.messageIndex;
|
||||
if(offset === 0 && this.props.segmentIndices.length === 2) {
|
||||
offset = this.props.segmentIndices[0];
|
||||
}
|
||||
if((offset + index) < this.props.message.entries.length) {
|
||||
return this.renderLogListItemMessage(this.props.message.entries[offset + index], key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderLogList(items, ref) {
|
||||
return (
|
||||
<div className='signals-log-list'>
|
||||
<div className='signals-log-list-header'>
|
||||
<div className='signals-log-list-message'>Message</div>
|
||||
<div className='signals-log-list-time'>Time</div>
|
||||
<div className='signals-log-list-bytes'>Bytes</div>
|
||||
</div>
|
||||
<div className='signals-log-list-items'
|
||||
ref={ref}>
|
||||
{items}
|
||||
</div>
|
||||
</div>
|
||||
const msgHasSignals =
|
||||
Object.keys(this.props.message.frame.signals).length > 0;
|
||||
if (msgIsExpanded && msgHasSignals) {
|
||||
this.setState({
|
||||
expandedMessages: this.state.expandedMessages.filter(
|
||||
expMsgTime => expMsgTime !== msgEntry.time
|
||||
)
|
||||
});
|
||||
} else if (msgHasSignals) {
|
||||
this.setState({
|
||||
expandedMessages: this.state.expandedMessages.concat([msgEntry.time])
|
||||
});
|
||||
this.props.onMessageExpanded();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
listLength() {
|
||||
const {segmentIndices, messageIndex} = this.props;
|
||||
if(messageIndex > 0) {
|
||||
return this.props.message.entries.length - messageIndex;
|
||||
} else if(segmentIndices.length === 2) {
|
||||
return segmentIndices[1] - segmentIndices[0];
|
||||
} else if(this.props.message) {
|
||||
return this.props.message.entries.length;
|
||||
} else {
|
||||
// no message yet
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
onExpandAllChanged(e) {
|
||||
this.setState({allPacketsExpanded: e.target.checked});
|
||||
}
|
||||
|
||||
toggleExpandAllPackets() {
|
||||
this.setState({allPacketsExpanded: !this.state.allPacketsExpanded});
|
||||
}
|
||||
|
||||
render() {
|
||||
let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All';
|
||||
let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha';
|
||||
return (
|
||||
<div className='cabana-explorer-signals-log'>
|
||||
<div className='cabana-explorer-signals-log-header'>
|
||||
<strong>Message Packets</strong>
|
||||
<button className={cx('button--tiny', expandAllClass)}
|
||||
onClick={this.toggleExpandAllPackets}>
|
||||
{expandAllText}
|
||||
</button>
|
||||
</div>
|
||||
<div className='cabana-explorer-signals-log-body'>
|
||||
<ReactList
|
||||
itemRenderer={this.renderLogListItem}
|
||||
itemsRenderer={this.renderLogList}
|
||||
length={this.listLength()}
|
||||
pageSize={50}
|
||||
updateWhenThisValueChanges={this.props.messageIndex}
|
||||
type='variable' />
|
||||
renderLogListItemSignals(msgEntry) {
|
||||
const { message } = this.props;
|
||||
return (
|
||||
<div className="signals-log-list-signals">
|
||||
{Object.entries(msgEntry.signals).map(([name, value]) => {
|
||||
const signal = message.frame.signals[name];
|
||||
if (signal === undefined) {
|
||||
// Signal removed?
|
||||
return null;
|
||||
}
|
||||
const unit = signal.unit.length > 0 ? signal.unit : "units";
|
||||
const isPlotted = this.isSignalPlotted(message.id, signal.uid);
|
||||
const plottedButtonClass = isPlotted ? null : "button--alpha";
|
||||
const plottedButtonText = isPlotted ? "Hide Plot" : "Show Plot";
|
||||
return (
|
||||
<div key={name} className="signals-log-list-signal">
|
||||
<div className="signals-log-list-signal-message">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
<div className="signals-log-list-signal-value">
|
||||
<span>
|
||||
(<strong>{this.signalValuePretty(signal, value)}</strong>{" "}
|
||||
{unit})
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="signals-log-list-signal-action"
|
||||
onClick={() => {
|
||||
this.toggleSignalPlot(message.id, signal.uid, isPlotted);
|
||||
}}
|
||||
>
|
||||
<button className={cx("button--tiny", plottedButtonClass)}>
|
||||
<span>{plottedButtonText}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLogListItemMessage(msgEntry, key) {
|
||||
const { message } = this.props;
|
||||
const msgIsExpanded =
|
||||
this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
||||
const msgHasSignals = Object.keys(msgEntry.signals).length > 0;
|
||||
const hasSignalsClass = msgHasSignals ? "has-signals" : null;
|
||||
const expandedClass = msgIsExpanded ? "is-expanded" : null;
|
||||
const row = (
|
||||
<div
|
||||
key={key}
|
||||
className={cx("signals-log-list-item", hasSignalsClass, expandedClass)}
|
||||
>
|
||||
<div
|
||||
className="signals-log-list-item-header"
|
||||
onClick={() => {
|
||||
this.toggleExpandPacketSignals(msgEntry);
|
||||
}}
|
||||
>
|
||||
<div className="signals-log-list-message">
|
||||
<strong>
|
||||
{(message.frame ? message.frame.name : null) || message.id}
|
||||
</strong>
|
||||
</div>
|
||||
);
|
||||
<div className="signals-log-list-time">
|
||||
<span>[{msgEntry.relTime.toFixed(3)}]</span>
|
||||
</div>
|
||||
<div className="signals-log-list-bytes">
|
||||
<span className="t-mono">{msgEntry.hexData}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="signals-log-list-item-body">
|
||||
{msgIsExpanded ? this.renderLogListItemSignals(msgEntry) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
renderLogListItem(index, key) {
|
||||
let offset = this.props.messageIndex;
|
||||
if (offset === 0 && this.props.segmentIndices.length === 2) {
|
||||
offset = this.props.segmentIndices[0];
|
||||
}
|
||||
if (offset + index < this.props.message.entries.length) {
|
||||
return this.renderLogListItemMessage(
|
||||
this.props.message.entries[offset + index],
|
||||
key
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderLogList(items, ref) {
|
||||
return (
|
||||
<div className="signals-log-list">
|
||||
<div className="signals-log-list-header">
|
||||
<div className="signals-log-list-message">Message</div>
|
||||
<div className="signals-log-list-time">Time</div>
|
||||
<div className="signals-log-list-bytes">Bytes</div>
|
||||
</div>
|
||||
<div className="signals-log-list-items" ref={ref}>
|
||||
{items}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
listLength() {
|
||||
const { segmentIndices, messageIndex } = this.props;
|
||||
if (messageIndex > 0) {
|
||||
return this.props.message.entries.length - messageIndex;
|
||||
} else if (segmentIndices.length === 2) {
|
||||
return segmentIndices[1] - segmentIndices[0];
|
||||
} else if (this.props.message) {
|
||||
return this.props.message.entries.length;
|
||||
} else {
|
||||
// no message yet
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
onExpandAllChanged(e) {
|
||||
this.setState({ allPacketsExpanded: e.target.checked });
|
||||
}
|
||||
|
||||
toggleExpandAllPackets() {
|
||||
this.setState({ allPacketsExpanded: !this.state.allPacketsExpanded });
|
||||
}
|
||||
|
||||
render() {
|
||||
let expandAllText = this.state.allPacketsExpanded
|
||||
? "Collapse All"
|
||||
: "Expand All";
|
||||
let expandAllClass = this.state.allPacketsExpanded ? null : "button--alpha";
|
||||
return (
|
||||
<div className="cabana-explorer-signals-log">
|
||||
<div className="cabana-explorer-signals-log-header">
|
||||
<strong>Message Packets</strong>
|
||||
<button
|
||||
className={cx("button--tiny", expandAllClass)}
|
||||
onClick={this.toggleExpandAllPackets}
|
||||
>
|
||||
{expandAllText}
|
||||
</button>
|
||||
</div>
|
||||
<div className="cabana-explorer-signals-log-body">
|
||||
<ReactList
|
||||
itemRenderer={this.renderLogListItem}
|
||||
itemsRenderer={this.renderLogList}
|
||||
length={this.listLength()}
|
||||
pageSize={50}
|
||||
updateWhenThisValueChanges={this.props.messageIndex}
|
||||
type="variable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,42 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default class DbcUpload extends Component {
|
||||
static propTypes = {
|
||||
onDbcLoaded: PropTypes.func
|
||||
static propTypes = {
|
||||
onDbcLoaded: PropTypes.func
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dbcText: ""
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dbcText: ''
|
||||
};
|
||||
|
||||
this.onTextChanged = this.onTextChanged.bind(this);
|
||||
}
|
||||
this.onTextChanged = this.onTextChanged.bind(this);
|
||||
}
|
||||
|
||||
onTextChanged(e) {
|
||||
const dbcText = e.target.value;
|
||||
this.setState({dbcText})
|
||||
this.props.onDbcLoaded('from paste', dbcText);
|
||||
}
|
||||
onTextChanged(e) {
|
||||
const dbcText = e.target.value;
|
||||
this.setState({ dbcText });
|
||||
this.props.onDbcLoaded("from paste", dbcText);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='cabana-dbc-upload-raw'>
|
||||
<div className='form-field'>
|
||||
<label htmlFor='raw_dbc_upload'>
|
||||
<span>Raw DBC File:</span>
|
||||
<sup>Paste your DBC text output within this box</sup>
|
||||
</label>
|
||||
<textarea value={ this.state.dbcText }
|
||||
id='raw_dbc_upload'
|
||||
className='t-mono'
|
||||
placeholder='PASTE DBC FILE HERE'
|
||||
onChange={ this.onTextChanged } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="cabana-dbc-upload-raw">
|
||||
<div className="form-field">
|
||||
<label htmlFor="raw_dbc_upload">
|
||||
<span>Raw DBC File:</span>
|
||||
<sup>Paste your DBC text output within this box</sup>
|
||||
</label>
|
||||
<textarea
|
||||
value={this.state.dbcText}
|
||||
id="raw_dbc_upload"
|
||||
className="t-mono"
|
||||
placeholder="PASTE DBC FILE HERE"
|
||||
onChange={this.onTextChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,123 +1,130 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import Modal from './Modals/baseModal';
|
||||
import Modal from "./Modals/baseModal";
|
||||
|
||||
export default class EditMessageModal extends Component {
|
||||
static propTypes = {
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
handleSave: PropTypes.func.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
static propTypes = {
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
handleSave: PropTypes.func.isRequired,
|
||||
message: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
messageFrame: props.message.frame.copy()
|
||||
};
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.editTransmitter = this.editTransmitter.bind(this);
|
||||
this.addTransmitter = this.addTransmitter.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
handleSave() {
|
||||
this.props.handleSave(this.state.messageFrame);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
messageFrame: props.message.frame.copy()
|
||||
}
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.editTransmitter = this.editTransmitter.bind(this);
|
||||
this.addTransmitter = this.addTransmitter.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
}
|
||||
addTransmitter() {
|
||||
const { messageFrame } = this.state;
|
||||
messageFrame.addTransmitter();
|
||||
this.setState({ messageFrame });
|
||||
}
|
||||
|
||||
handleSave() {
|
||||
this.props.handleSave(this.state.messageFrame);
|
||||
}
|
||||
editTransmitter(transmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
addTransmitter() {
|
||||
const {messageFrame} = this.state;
|
||||
messageFrame.addTransmitter();
|
||||
this.setState({messageFrame});
|
||||
}
|
||||
renderActions() {
|
||||
return (
|
||||
<div>
|
||||
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="button--primary" onClick={this.handleSave}>
|
||||
Save Message
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
editTransmitter(transmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderActions() {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className='button--inverted'
|
||||
onClick={ this.props.handleClose }>Cancel</button>
|
||||
<button
|
||||
className='button--primary'
|
||||
onClick={ this.handleSave }>Save Message</button>
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title={`Edit Message: (${this.props.message.id})`}
|
||||
subtitle="Make changes and update defaults of this message"
|
||||
handleClose={this.props.handleClose}
|
||||
handleSave={this.handleSave}
|
||||
actions={this.renderActions()}
|
||||
>
|
||||
<div className="form-field">
|
||||
<label htmlFor="message_name">
|
||||
<span>Name</span>
|
||||
<sup>Customize the name of this message</sup>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="message_name"
|
||||
value={this.state.messageFrame.name}
|
||||
onChange={e => {
|
||||
const { messageFrame } = this.state;
|
||||
messageFrame.name = e.target.value;
|
||||
this.setState({ messageFrame });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title={ `Edit Message: (${ this.props.message.id })`}
|
||||
subtitle='Make changes and update defaults of this message'
|
||||
handleClose={ this.props.handleClose }
|
||||
handleSave={ this.handleSave }
|
||||
actions={ this.renderActions() }>
|
||||
<div className='form-field'>
|
||||
<label htmlFor='message_name'>
|
||||
<span>Name</span>
|
||||
<sup>Customize the name of this message</sup>
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
id='message_name'
|
||||
value={ this.state.messageFrame.name }
|
||||
onChange={ (e) => {
|
||||
const { messageFrame } = this.state;
|
||||
messageFrame.name = e.target.value;
|
||||
this.setState({ messageFrame });
|
||||
}} />
|
||||
</div>
|
||||
<div className='form-field'>
|
||||
<label htmlFor='message_size'>
|
||||
<span>Size</span>
|
||||
<sup>Add a size parameter to this message</sup>
|
||||
</label>
|
||||
<input
|
||||
type='number'
|
||||
id='message_size'
|
||||
value={this.state.messageFrame.size}
|
||||
onChange={(e) => {
|
||||
const {messageFrame} = this.state;
|
||||
if(e.target.value > 8) {
|
||||
return;
|
||||
}
|
||||
messageFrame.size = parseInt(e.target.value, 10);
|
||||
this.setState({ messageFrame });
|
||||
}} />
|
||||
</div>
|
||||
<div className='form-field u-hidden'>
|
||||
<label htmlFor='message_transmitters'>
|
||||
<span>Transmitters</span>
|
||||
<sup>Add the physical ECU units that this message is coming from.</sup>
|
||||
</label>
|
||||
<div className='form-field-inset'>
|
||||
<ul className='form-field-inset-list'>
|
||||
{ this.state.messageFrame.transmitters.map((transmitter) => {
|
||||
return (
|
||||
<li className='form-field-inset-list-item' key={ transmitter }>
|
||||
<div className='form-field-inset-list-item-title'>
|
||||
<span>{ transmitter }</span>
|
||||
</div>
|
||||
<div className='form-field-inset-list-item-action'>
|
||||
<button className='button--tiny button--alpha'>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}) }
|
||||
<button className='button--tiny button--alpha'>
|
||||
<span><i className='fa fa-plus'></i> Add Transmitter</span>
|
||||
</button>
|
||||
</ul>
|
||||
<div className="form-field">
|
||||
<label htmlFor="message_size">
|
||||
<span>Size</span>
|
||||
<sup>Add a size parameter to this message</sup>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="message_size"
|
||||
value={this.state.messageFrame.size}
|
||||
onChange={e => {
|
||||
const { messageFrame } = this.state;
|
||||
if (e.target.value > 8) {
|
||||
return;
|
||||
}
|
||||
messageFrame.size = parseInt(e.target.value, 10);
|
||||
this.setState({ messageFrame });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field u-hidden">
|
||||
<label htmlFor="message_transmitters">
|
||||
<span>Transmitters</span>
|
||||
<sup>
|
||||
Add the physical ECU units that this message is coming from.
|
||||
</sup>
|
||||
</label>
|
||||
<div className="form-field-inset">
|
||||
<ul className="form-field-inset-list">
|
||||
{this.state.messageFrame.transmitters.map(transmitter => {
|
||||
return (
|
||||
<li className="form-field-inset-list-item" key={transmitter}>
|
||||
<div className="form-field-inset-list-item-title">
|
||||
<span>{transmitter}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
<div className="form-field-inset-list-item-action">
|
||||
<button className="button--tiny button--alpha">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<button className="button--tiny button--alpha">
|
||||
<span>
|
||||
<i className="fa fa-plus" /> Add Transmitter
|
||||
</span>
|
||||
</button>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,84 +1,97 @@
|
|||
import React, {Component} from 'react';
|
||||
import cx from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import cx from "classnames";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import OpenDbc from '../api/OpenDbc';
|
||||
import OpenDbc from "../api/OpenDbc";
|
||||
|
||||
export default class GithubDbcList extends Component {
|
||||
static propTypes = {
|
||||
onDbcLoaded: PropTypes.func.isRequired,
|
||||
repo: PropTypes.string.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
|
||||
static propTypes = {
|
||||
onDbcLoaded: PropTypes.func.isRequired,
|
||||
repo: PropTypes.string.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
paths: [],
|
||||
selectedPath: null,
|
||||
pathQuery: ""
|
||||
};
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.updatePathQuery = this.updatePathQuery.bind(this);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
paths: [],
|
||||
selectedPath: null,
|
||||
pathQuery: ''
|
||||
};
|
||||
|
||||
this.updatePathQuery = this.updatePathQuery.bind(this);
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.repo !== this.props.repo) {
|
||||
this.props.openDbcClient.list(nextProps.repo).then(paths => {
|
||||
this.setState({ paths, selectedPath: null });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.repo !== this.props.repo) {
|
||||
this.props.openDbcClient.list(nextProps.repo).then((paths) => {
|
||||
this.setState({paths, selectedPath: null})
|
||||
})
|
||||
}
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.openDbcClient.list(this.props.repo).then(paths => {
|
||||
paths = paths.filter(path => path.indexOf(".dbc") !== -1);
|
||||
this.setState({ paths });
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.openDbcClient.list(this.props.repo).then((paths) => {
|
||||
paths = paths.filter((path) => path.indexOf(".dbc") !== -1);
|
||||
this.setState({paths});
|
||||
})
|
||||
}
|
||||
updatePathQuery(e) {
|
||||
this.setState({
|
||||
pathQuery: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
updatePathQuery(e) {
|
||||
this.setState({
|
||||
pathQuery: e.target.value,
|
||||
})
|
||||
}
|
||||
selectPath(path) {
|
||||
this.setState({ selectedPath: path });
|
||||
this.props.openDbcClient
|
||||
.getDbcContents(path, this.props.repo)
|
||||
.then(dbcContents => {
|
||||
this.props.onDbcLoaded(path, dbcContents);
|
||||
});
|
||||
}
|
||||
|
||||
selectPath(path) {
|
||||
this.setState({selectedPath: path})
|
||||
this.props.openDbcClient.getDbcContents(path, this.props.repo).then((dbcContents) => {
|
||||
this.props.onDbcLoaded(path, dbcContents);
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='cabana-dbc-list'>
|
||||
<div className='cabana-dbc-list-header'>
|
||||
<a href={ `https://github.com/${ this.props.repo }` }
|
||||
target='_blank'>
|
||||
<i className='fa fa-github'></i>
|
||||
<span>{ this.props.repo }</span>
|
||||
</a>
|
||||
<div className='form-field form-field--small'>
|
||||
<input type='text'
|
||||
placeholder='Search DBC Files'
|
||||
onChange={ this.updatePathQuery }/>
|
||||
</div>
|
||||
render() {
|
||||
return (
|
||||
<div className="cabana-dbc-list">
|
||||
<div className="cabana-dbc-list-header">
|
||||
<a href={`https://github.com/${this.props.repo}`} target="_blank">
|
||||
<i className="fa fa-github" />
|
||||
<span>{this.props.repo}</span>
|
||||
</a>
|
||||
<div className="form-field form-field--small">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search DBC Files"
|
||||
onChange={this.updatePathQuery}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cabana-dbc-list-files">
|
||||
{this.state.paths
|
||||
.filter(
|
||||
p =>
|
||||
(this.state.pathQuery === "") | p.includes(this.state.pathQuery)
|
||||
)
|
||||
.map(path => {
|
||||
return (
|
||||
<div
|
||||
className={cx("cabana-dbc-list-file", {
|
||||
"is-selected": this.state.selectedPath === path
|
||||
})}
|
||||
onClick={() => {
|
||||
this.selectPath(path);
|
||||
}}
|
||||
key={path}
|
||||
>
|
||||
<span>{path}</span>
|
||||
</div>
|
||||
<div className='cabana-dbc-list-files'>
|
||||
{ this.state.paths.filter(p => this.state.pathQuery === '' | p.includes(this.state.pathQuery))
|
||||
.map((path) => {
|
||||
return (
|
||||
<div className={ cx('cabana-dbc-list-file', {'is-selected': this.state.selectedPath === path})}
|
||||
onClick={ () => { this.selectPath(path) } }
|
||||
key={path}>
|
||||
<span>{ path }</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Hls from 'hls.js/lib';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Hls from "hls.js/lib";
|
||||
|
||||
export default class HLS extends Component {
|
||||
static propTypes = {
|
||||
|
@ -19,13 +19,16 @@ export default class HLS extends Component {
|
|||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if( (nextProps.shouldRestart || nextProps.startTime !== this.props.startTime)
|
||||
&& isFinite(nextProps.startTime)) {
|
||||
if (
|
||||
(nextProps.shouldRestart ||
|
||||
nextProps.startTime !== this.props.startTime) &&
|
||||
isFinite(nextProps.startTime)
|
||||
) {
|
||||
this.videoElement.currentTime = nextProps.startTime;
|
||||
this.props.onRestart();
|
||||
}
|
||||
|
||||
if(nextProps.playing) {
|
||||
if (nextProps.playing) {
|
||||
this.videoElement.play();
|
||||
} else {
|
||||
this.videoElement.pause();
|
||||
|
@ -37,20 +40,20 @@ export default class HLS extends Component {
|
|||
this.player.loadSource(this.props.source);
|
||||
this.player.attachMedia(this.videoElement);
|
||||
// these events fire when video is playing
|
||||
this.videoElement.addEventListener('waiting', this.props.onLoadStart);
|
||||
this.videoElement.addEventListener('playing', this.props.onLoadEnd);
|
||||
this.videoElement.addEventListener("waiting", this.props.onLoadStart);
|
||||
this.videoElement.addEventListener("playing", this.props.onLoadEnd);
|
||||
|
||||
// these events fire when video is paused & seeked
|
||||
this.videoElement.addEventListener('seeking', () => {
|
||||
if(!this.props.playing) {
|
||||
this.videoElement.addEventListener("seeking", () => {
|
||||
if (!this.props.playing) {
|
||||
this.props.onLoadStart();
|
||||
this.props.onPlaySeek(this.videoElement.currentTime);
|
||||
}
|
||||
});
|
||||
let shouldInitVideoTime = true;
|
||||
this.videoElement.addEventListener('seeked', () => {
|
||||
if(!this.props.playing) {
|
||||
if(shouldInitVideoTime) {
|
||||
this.videoElement.addEventListener("seeked", () => {
|
||||
if (!this.props.playing) {
|
||||
if (shouldInitVideoTime) {
|
||||
this.videoElement.currentTime = this.props.startTime;
|
||||
shouldInitVideoTime = false;
|
||||
}
|
||||
|
@ -59,7 +62,7 @@ export default class HLS extends Component {
|
|||
});
|
||||
|
||||
this.props.onVideoElementAvailable(this.videoElement);
|
||||
if(this.props.playing) {
|
||||
if (this.props.playing) {
|
||||
this.videoElement.play();
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +70,14 @@ export default class HLS extends Component {
|
|||
render() {
|
||||
return (
|
||||
<div
|
||||
className='cabana-explorer-visuals-camera-wrapper'
|
||||
onClick={this.props.onClick}>
|
||||
<video ref={ (video) => { this.videoElement = video; } } />
|
||||
className="cabana-explorer-visuals-camera-wrapper"
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<video
|
||||
ref={video => {
|
||||
this.videoElement = video;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,127 +1,129 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
|
||||
import DBC from '../models/can/dbc';
|
||||
import OpenDbc from '../api/OpenDbc';
|
||||
import Modal from './Modals/baseModal';
|
||||
import GithubDbcList from './GithubDbcList';
|
||||
import DbcUpload from './DbcUpload';
|
||||
import DBC from "../models/can/dbc";
|
||||
import OpenDbc from "../api/OpenDbc";
|
||||
import Modal from "./Modals/baseModal";
|
||||
import GithubDbcList from "./GithubDbcList";
|
||||
import DbcUpload from "./DbcUpload";
|
||||
|
||||
export default class LoadDbcModal extends Component {
|
||||
static propTypes = {
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
onDbcSelected: PropTypes.func.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||
loginWithGithub: PropTypes.element.isRequired,
|
||||
static propTypes = {
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
onDbcSelected: PropTypes.func.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||
loginWithGithub: PropTypes.element.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tab: "OpenDBC",
|
||||
tabs: ["OpenDBC", "GitHub", "Upload"],
|
||||
dbc: null,
|
||||
dbcSource: null,
|
||||
userOpenDbcRepo: null
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tab: 'OpenDBC',
|
||||
tabs: ['OpenDBC', 'GitHub', 'Upload'],
|
||||
dbc: null,
|
||||
dbcSource: null,
|
||||
userOpenDbcRepo: null,
|
||||
}
|
||||
this.onDbcLoaded = this.onDbcLoaded.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||
this.renderTabContent = this.renderTabContent.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
}
|
||||
|
||||
this.onDbcLoaded = this.onDbcLoaded.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||
this.renderTabContent = this.renderTabContent.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
componentWillMount() {
|
||||
this.props.openDbcClient.getUserOpenDbcFork().then(userOpenDbcRepo => {
|
||||
this.setState({ userOpenDbcRepo });
|
||||
});
|
||||
}
|
||||
|
||||
onDbcLoaded(dbcSource, dbcText) {
|
||||
const dbc = new DBC(dbcText);
|
||||
this.setState({ dbcSource, dbc });
|
||||
}
|
||||
|
||||
handleSave() {
|
||||
const { dbc, dbcSource } = this.state;
|
||||
if (dbc !== null) {
|
||||
this.props.onDbcSelected(dbcSource, dbc);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.openDbcClient.getUserOpenDbcFork().then((userOpenDbcRepo) => {
|
||||
this.setState({userOpenDbcRepo});
|
||||
});
|
||||
}
|
||||
|
||||
onDbcLoaded(dbcSource, dbcText) {
|
||||
const dbc = new DBC(dbcText);
|
||||
this.setState({dbcSource, dbc})
|
||||
}
|
||||
|
||||
handleSave() {
|
||||
const { dbc, dbcSource } = this.state;
|
||||
if(dbc !== null) {
|
||||
this.props.onDbcSelected(dbcSource, dbc);
|
||||
}
|
||||
}
|
||||
|
||||
renderTabNavigation() {
|
||||
return (
|
||||
<div className='cabana-tabs-navigation'>
|
||||
{ this.state.tabs.map((tab) => {
|
||||
return (
|
||||
<a className={ cx({'is-active': this.state.tab === tab})}
|
||||
onClick={ () => { this.setState({ tab }) }}
|
||||
key={tab}>
|
||||
<span>{ tab }</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTabContent() {
|
||||
const { tab } = this.state;
|
||||
if (tab === 'OpenDBC') {
|
||||
return (
|
||||
<GithubDbcList
|
||||
onDbcLoaded={ this.onDbcLoaded }
|
||||
repo="commaai/opendbc"
|
||||
openDbcClient={ this.props.openDbcClient } />
|
||||
);
|
||||
} else if (tab === 'GitHub') {
|
||||
if (!this.props.openDbcClient.hasAuth()) {
|
||||
return this.props.loginWithGithub;
|
||||
} else if (this.state.userOpenDbcRepo === null) {
|
||||
return (<div>Fork it</div>);
|
||||
} else {
|
||||
return (
|
||||
<GithubDbcList
|
||||
onDbcLoaded={this.onDbcLoaded}
|
||||
repo={this.state.userOpenDbcRepo}
|
||||
openDbcClient={this.props.openDbcClient} />
|
||||
);
|
||||
}
|
||||
} else if (tab === 'Upload') {
|
||||
renderTabNavigation() {
|
||||
return (
|
||||
<div className="cabana-tabs-navigation">
|
||||
{this.state.tabs.map(tab => {
|
||||
return (
|
||||
<DbcUpload
|
||||
onDbcLoaded={this.onDbcLoaded} />
|
||||
<a
|
||||
className={cx({ "is-active": this.state.tab === tab })}
|
||||
onClick={() => {
|
||||
this.setState({ tab });
|
||||
}}
|
||||
key={tab}
|
||||
>
|
||||
<span>{tab}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions() {
|
||||
renderTabContent() {
|
||||
const { tab } = this.state;
|
||||
if (tab === "OpenDBC") {
|
||||
return (
|
||||
<GithubDbcList
|
||||
onDbcLoaded={this.onDbcLoaded}
|
||||
repo="commaai/opendbc"
|
||||
openDbcClient={this.props.openDbcClient}
|
||||
/>
|
||||
);
|
||||
} else if (tab === "GitHub") {
|
||||
if (!this.props.openDbcClient.hasAuth()) {
|
||||
return this.props.loginWithGithub;
|
||||
} else if (this.state.userOpenDbcRepo === null) {
|
||||
return <div>Fork it</div>;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<button className='button--inverted'
|
||||
onClick={ this.props.handleClose }>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className='button--primary'
|
||||
onClick={ this.handleSave }>
|
||||
<span>Load DBC</span>
|
||||
</button>
|
||||
</div>
|
||||
<GithubDbcList
|
||||
onDbcLoaded={this.onDbcLoaded}
|
||||
repo={this.state.userOpenDbcRepo}
|
||||
openDbcClient={this.props.openDbcClient}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (tab === "Upload") {
|
||||
return <DbcUpload onDbcLoaded={this.onDbcLoaded} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title='Load DBC File'
|
||||
subtitle='Modify an existing DBC file with Cabana'
|
||||
handleClose={ this.props.handleClose }
|
||||
navigation={ this.renderTabNavigation() }
|
||||
actions={ this.renderActions() }>
|
||||
{ this.renderTabContent() }
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
renderActions() {
|
||||
return (
|
||||
<div>
|
||||
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className="button--primary" onClick={this.handleSave}>
|
||||
<span>Load DBC</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title="Load DBC File"
|
||||
subtitle="Modify an existing DBC file with Cabana"
|
||||
handleClose={this.props.handleClose}
|
||||
navigation={this.renderTabNavigation()}
|
||||
actions={this.renderActions()}
|
||||
>
|
||||
{this.renderTabContent()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
import React, { Component } from 'react';
|
||||
import { css, StyleSheet } from 'aphrodite/no-important';
|
||||
import React, { Component } from "react";
|
||||
import { css, StyleSheet } from "aphrodite/no-important";
|
||||
|
||||
const keyframes = {
|
||||
'0%': {
|
||||
transform: 'translateX(0)'
|
||||
},
|
||||
to: {
|
||||
transform: 'translateX(-400px)'
|
||||
}
|
||||
"0%": {
|
||||
transform: "translateX(0)"
|
||||
},
|
||||
to: {
|
||||
transform: "translateX(-400px)"
|
||||
}
|
||||
};
|
||||
const animationColor1 = 'RGBA(74, 242, 161, 1.00)';
|
||||
const animationColor2 = 'RGBA(140, 169, 197, 1.00)';
|
||||
const animationColor1 = "RGBA(74, 242, 161, 1.00)";
|
||||
const animationColor2 = "RGBA(140, 169, 197, 1.00)";
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
loadingBar: {
|
||||
display: 'block',
|
||||
animationName: [keyframes],
|
||||
animationDuration: '2s',
|
||||
animationTimingFunction: 'linear',
|
||||
animationIterationCount: 'infinite',
|
||||
backgroundColor: animationColor1,
|
||||
backgroundImage: `linear-gradient(to right,
|
||||
loadingBar: {
|
||||
display: "block",
|
||||
animationName: [keyframes],
|
||||
animationDuration: "2s",
|
||||
animationTimingFunction: "linear",
|
||||
animationIterationCount: "infinite",
|
||||
backgroundColor: animationColor1,
|
||||
backgroundImage: `linear-gradient(to right,
|
||||
${animationColor2} 0,
|
||||
${animationColor2} 50%,
|
||||
${animationColor1} 50%,
|
||||
${animationColor1} 100%)`,
|
||||
backgroundRepeat: 'repeat-x',
|
||||
backgroundSize: '25pc 25pc',
|
||||
width: '200%',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 2
|
||||
}
|
||||
backgroundRepeat: "repeat-x",
|
||||
backgroundSize: "25pc 25pc",
|
||||
width: "200%",
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 2
|
||||
}
|
||||
});
|
||||
|
||||
export default class LoadingBar extends Component {
|
||||
render() {
|
||||
return (<div className={css(Styles.loadingBar)}></div>)
|
||||
}
|
||||
render() {
|
||||
return <div className={css(Styles.loadingBar)} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +1,124 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default class MessageBytes extends Component {
|
||||
static propTypes = {
|
||||
seekTime: PropTypes.number.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
seekIndex: PropTypes.number,
|
||||
live: PropTypes.bool.isRequired,
|
||||
static propTypes = {
|
||||
seekTime: PropTypes.number.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
seekIndex: PropTypes.number,
|
||||
live: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isVisible: true,
|
||||
lastMessageIndex: 0,
|
||||
lastSeekTime: 0
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isVisible: true,
|
||||
lastMessageIndex: 0,
|
||||
lastSeekTime: 0,
|
||||
};
|
||||
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
||||
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
|
||||
}
|
||||
|
||||
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
||||
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (nextProps.live) {
|
||||
const nextLastEntry =
|
||||
nextProps.message.entries[nextProps.message.entries.length - 1];
|
||||
const curLastEntry = this.props.message.entries[
|
||||
this.props.message.entries.length - 1
|
||||
];
|
||||
|
||||
return nextLastEntry.hexData !== curLastEntry.hexData;
|
||||
} else {
|
||||
return nextProps.seekTime !== this.props.seekTime;
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if(nextProps.live) {
|
||||
const nextLastEntry = nextProps.message.entries[nextProps.message.entries.length - 1];
|
||||
const curLastEntry = this.props.message.entries[this.props.message.entries.length - 1];
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.updateCanvas(nextProps);
|
||||
}
|
||||
|
||||
return (nextLastEntry.hexData !== curLastEntry.hexData);
|
||||
} else {
|
||||
return nextProps.seekTime !== this.props.seekTime
|
||||
findMostRecentMessage(seekTime) {
|
||||
const { message } = this.props;
|
||||
const { lastMessageIndex, lastSeekTime } = this.state;
|
||||
let mostRecentMessageIndex = null;
|
||||
if (seekTime >= lastSeekTime) {
|
||||
for (let i = lastMessageIndex; i < message.entries.length; i++) {
|
||||
const msg = message.entries[i];
|
||||
if (msg && msg.relTime >= seekTime) {
|
||||
mostRecentMessageIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.updateCanvas(nextProps);
|
||||
if (!mostRecentMessageIndex) {
|
||||
// TODO this can be faster with binary search, not currently a bottleneck though.
|
||||
|
||||
mostRecentMessageIndex = message.entries.findIndex(
|
||||
e => e.relTime >= seekTime
|
||||
);
|
||||
}
|
||||
|
||||
findMostRecentMessage(seekTime) {
|
||||
const {message} = this.props;
|
||||
const {lastMessageIndex, lastSeekTime} = this.state;
|
||||
let mostRecentMessageIndex = null;
|
||||
if(seekTime >= lastSeekTime) {
|
||||
for(let i = lastMessageIndex; i < message.entries.length; i++) {
|
||||
const msg = message.entries[i];
|
||||
if(msg && msg.relTime >= seekTime) {
|
||||
mostRecentMessageIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mostRecentMessageIndex) {
|
||||
this.setState({
|
||||
lastMessageIndex: mostRecentMessageIndex,
|
||||
lastSeekTime: seekTime
|
||||
});
|
||||
return message.entries[mostRecentMessageIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if(!mostRecentMessageIndex) {
|
||||
// TODO this can be faster with binary search, not currently a bottleneck though.
|
||||
updateCanvas(props) {
|
||||
const { message, live, seekTime } = props;
|
||||
if (!this.canvas || message.entries.length === 0) return;
|
||||
|
||||
mostRecentMessageIndex = message.entries.findIndex((e) => e.relTime >= seekTime);
|
||||
}
|
||||
let mostRecentMsg = message.entries[message.entries.length - 1];
|
||||
if (!live) {
|
||||
mostRecentMsg = this.findMostRecentMessage(seekTime);
|
||||
|
||||
if(mostRecentMessageIndex) {
|
||||
this.setState({lastMessageIndex: mostRecentMessageIndex, lastSeekTime: seekTime});
|
||||
return message.entries[mostRecentMessageIndex];
|
||||
}
|
||||
if (!mostRecentMsg) {
|
||||
mostRecentMsg = message.entries[0];
|
||||
}
|
||||
}
|
||||
|
||||
updateCanvas(props) {
|
||||
const {message, live, seekTime} = props;
|
||||
if(!this.canvas || message.entries.length === 0) return;
|
||||
const ctx = this.canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, 180, 15);
|
||||
for (let i = 0; i < message.byteStateChangeCounts.length; i++) {
|
||||
const hexData = mostRecentMsg.hexData.substr(i * 2, 2);
|
||||
ctx.fillStyle = message.byteColors[i];
|
||||
|
||||
let mostRecentMsg = message.entries[message.entries.length - 1];
|
||||
if(!live) {
|
||||
mostRecentMsg = this.findMostRecentMessage(seekTime);
|
||||
ctx.fillRect(i * 20, 0, 20, 15);
|
||||
|
||||
if(!mostRecentMsg) {
|
||||
mostRecentMsg = message.entries[0];
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
ctx.clearRect(0,0,180,15);
|
||||
for(let i = 0; i < message.byteStateChangeCounts.length; i++) {
|
||||
const hexData = mostRecentMsg.hexData.substr(i * 2, 2);
|
||||
ctx.fillStyle = message.byteColors[i];
|
||||
|
||||
|
||||
ctx.fillRect(i * 20, 0, 20, 15);
|
||||
|
||||
ctx.font = '12px Courier';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillText(hexData, i * 20 + 2, 12);
|
||||
}
|
||||
ctx.font = "12px Courier";
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fillText(hexData, i * 20 + 2, 12);
|
||||
}
|
||||
}
|
||||
|
||||
onVisibilityChange(isVisible) {
|
||||
if(isVisible !== this.state.isVisible) {
|
||||
this.setState({isVisible});
|
||||
}
|
||||
onVisibilityChange(isVisible) {
|
||||
if (isVisible !== this.state.isVisible) {
|
||||
this.setState({ isVisible });
|
||||
}
|
||||
}
|
||||
|
||||
onCanvasRefAvailable(ref) {
|
||||
if(!ref) return;
|
||||
onCanvasRefAvailable(ref) {
|
||||
if (!ref) return;
|
||||
|
||||
this.canvas = ref;
|
||||
this.canvas.width = 160 * window.devicePixelRatio;
|
||||
this.canvas.height = 15 * window.devicePixelRatio;
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
this.canvas = ref;
|
||||
this.canvas.width = 160 * window.devicePixelRatio;
|
||||
this.canvas.height = 15 * window.devicePixelRatio;
|
||||
const ctx = this.canvas.getContext("2d");
|
||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<canvas ref={this.onCanvasRefAvailable}
|
||||
className='cabana-meta-messages-list-item-bytes-canvas'></canvas>);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<canvas
|
||||
ref={this.onCanvasRefAvailable}
|
||||
className="cabana-meta-messages-list-item-bytes-canvas"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, {Component} from 'react';
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import { StyleSheet, css } from "aphrodite/no-important";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import GlobalStyles from '../styles/styles';
|
||||
import Images from '../styles/images';
|
||||
import GlobalStyles from "../styles/styles";
|
||||
import Images from "../styles/images";
|
||||
|
||||
export default class Modal extends Component {
|
||||
static propTypes = {
|
||||
|
@ -21,112 +21,119 @@ export default class Modal extends Component {
|
|||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
if(e.keyCode === 27){ // escape
|
||||
this.props.onCancel()
|
||||
if (e.keyCode === 27) {
|
||||
// escape
|
||||
this.props.onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
document.addEventListener("keydown", this._onKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
document.removeEventListener("keydown", this._onKeyDown);
|
||||
}
|
||||
|
||||
selectButton() {
|
||||
const {continueEnabled, continueText, onContinue} = this.props;
|
||||
const { continueEnabled, continueText, onContinue } = this.props;
|
||||
let style;
|
||||
if(continueEnabled) {
|
||||
if (continueEnabled) {
|
||||
style = Styles.selectButtonEnabled;
|
||||
} else {
|
||||
style = Styles.selectButtonDisabled;
|
||||
}
|
||||
|
||||
return (<div className={css(GlobalStyles.button, Styles.selectButton, style)}
|
||||
onClick={continueEnabled ? this.props.onContinue : () => {}}>
|
||||
<p>{continueText || 'Continue'}</p>
|
||||
</div>);
|
||||
return (
|
||||
<div
|
||||
className={css(GlobalStyles.button, Styles.selectButton, style)}
|
||||
onClick={continueEnabled ? this.props.onContinue : () => {}}
|
||||
>
|
||||
<p>{continueText || "Continue"}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className={css(Styles.root)}>
|
||||
<div className={css(Styles.bg)}></div>
|
||||
<div className={css(Styles.box)}>
|
||||
<div className={css(Styles.header)}>
|
||||
<p className={css(Styles.title)}>{this.props.title}</p>
|
||||
<Images.clear styles={[Styles.closeButton]}
|
||||
onClick={this.props.onCancel} />
|
||||
</div>
|
||||
{this.props.children}
|
||||
<div className={css(Styles.select)}>
|
||||
{this.selectButton()}
|
||||
<div className={css(Styles.finishButton, Styles.cancelButton)}>
|
||||
<p onClick={this.props.onCancel}>Cancel</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
return (
|
||||
<div className={css(Styles.root)}>
|
||||
<div className={css(Styles.bg)} />
|
||||
<div className={css(Styles.box)}>
|
||||
<div className={css(Styles.header)}>
|
||||
<p className={css(Styles.title)}>{this.props.title}</p>
|
||||
<Images.clear
|
||||
styles={[Styles.closeButton]}
|
||||
onClick={this.props.onCancel}
|
||||
/>
|
||||
</div>
|
||||
{this.props.children}
|
||||
<div className={css(Styles.select)}>
|
||||
{this.selectButton()}
|
||||
<div className={css(Styles.finishButton, Styles.cancelButton)}>
|
||||
<p onClick={this.props.onCancel}>Cancel</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
bg: {
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 9,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'white',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "white",
|
||||
opacity: 0.75
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
marginBottom: 10,
|
||||
marginRight: 'auto'
|
||||
},
|
||||
closeButton: {
|
||||
|
||||
marginRight: "auto"
|
||||
},
|
||||
closeButton: {},
|
||||
box: {
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 10,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #000',
|
||||
boxShadow: '1px 1px 1px #000',
|
||||
padding: 20,
|
||||
minWidth: 480
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
top: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
zIndex: 10,
|
||||
backgroundColor: "white",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #000",
|
||||
boxShadow: "1px 1px 1px #000",
|
||||
padding: 20,
|
||||
minWidth: 480
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end'
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end"
|
||||
},
|
||||
select: {
|
||||
paddingTop: 20,
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
},
|
||||
finishButton: {
|
||||
borderRadius: 5,
|
||||
height: 40,
|
||||
width: 80,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
},
|
||||
selectButton: {
|
||||
backgroundColor: 'rgb(77,144,254)',
|
||||
color: 'white'
|
||||
backgroundColor: "rgb(77,144,254)",
|
||||
color: "white"
|
||||
},
|
||||
selectButtonDisabled: {
|
||||
cursor: 'default',
|
||||
cursor: "default",
|
||||
opacity: 0.5
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,295 +1,376 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import cx from 'classnames';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Moment from "moment";
|
||||
import _ from "lodash";
|
||||
import cx from "classnames";
|
||||
|
||||
import auth from '../../api/comma-auth';
|
||||
import auth from "../../api/comma-auth";
|
||||
|
||||
import Modal from '../Modals/baseModal';
|
||||
import Modal from "../Modals/baseModal";
|
||||
|
||||
export default class OnboardingModal extends Component {
|
||||
static propTypes = {
|
||||
handlePandaConnect: PropTypes.func,
|
||||
routes: PropTypes.array,
|
||||
static propTypes = {
|
||||
handlePandaConnect: PropTypes.func,
|
||||
routes: PropTypes.array
|
||||
};
|
||||
|
||||
static instructionalImages = {
|
||||
step2: require("../../images/webusb-enable-experimental-features.png"),
|
||||
step3: require("../../images/webusb-enable-webusb.png")
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
webUsbEnabled: !!navigator.usb,
|
||||
viewingUsbInstructions: false,
|
||||
pandaConnected: false,
|
||||
chffrDrivesSearch: "",
|
||||
chffrDrivesSortBy: "start_time",
|
||||
chffrDrivesOrderDesc: true
|
||||
};
|
||||
|
||||
static instructionalImages = {
|
||||
step2: require("../../images/webusb-enable-experimental-features.png"),
|
||||
step3: require("../../images/webusb-enable-webusb.png"),
|
||||
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
|
||||
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
|
||||
this.handleSortDrives = this.handleSortDrives.bind(this);
|
||||
this.handleSearchDrives = this.handleSearchDrives.bind(this);
|
||||
this.navigateToAuth = this.navigateToAuth.bind(this);
|
||||
this.openChffrDrive = this.openChffrDrive.bind(this);
|
||||
}
|
||||
|
||||
attemptPandaConnection() {
|
||||
if (!this.state.webUsbEnabled) {
|
||||
return;
|
||||
}
|
||||
this.props.handlePandaConnect();
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
toggleUsbInstructions() {
|
||||
this.setState({
|
||||
viewingUsbInstructions: !this.state.viewingUsbInstructions
|
||||
});
|
||||
}
|
||||
|
||||
this.state = {
|
||||
webUsbEnabled: !!navigator.usb,
|
||||
viewingUsbInstructions: false,
|
||||
pandaConnected: false,
|
||||
chffrDrivesSearch: '',
|
||||
chffrDrivesSortBy: 'start_time',
|
||||
chffrDrivesOrderDesc: true,
|
||||
}
|
||||
navigateToAuth() {
|
||||
const authUrl = auth.authUrl();
|
||||
window.location.href = authUrl;
|
||||
}
|
||||
|
||||
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
|
||||
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
|
||||
this.handleSortDrives = this.handleSortDrives.bind(this);
|
||||
this.handleSearchDrives = this.handleSearchDrives.bind(this);
|
||||
this.navigateToAuth = this.navigateToAuth.bind(this);
|
||||
this.openChffrDrive = this.openChffrDrive.bind(this);
|
||||
filterRoutesWithCan(drive) {
|
||||
return drive.can === true;
|
||||
}
|
||||
|
||||
handleSearchDrives(drive) {
|
||||
const { chffrDrivesSearch } = this.state;
|
||||
const searchKeywords = chffrDrivesSearch
|
||||
.split(" ")
|
||||
.filter(s => s.length > 0)
|
||||
.map(s => s.toLowerCase());
|
||||
|
||||
return (
|
||||
searchKeywords.length === 0 ||
|
||||
searchKeywords.some(
|
||||
kw =>
|
||||
drive.end_geocode.toLowerCase().indexOf(kw) !== -1 ||
|
||||
drive.start_geocode.toLowerCase().indexOf(kw) !== -1 ||
|
||||
Moment(drive.start_time)
|
||||
.format("dddd MMMM Do YYYY")
|
||||
.toLowerCase()
|
||||
.indexOf(kw) !== -1 ||
|
||||
Moment(drive.end_time)
|
||||
.format("dddd MMMM Do YYYY")
|
||||
.toLowerCase()
|
||||
.indexOf(kw) !== -1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
handleSortDrives(key) {
|
||||
if (this.state.chffrDrivesSortBy === key) {
|
||||
this.setState({ chffrDrivesOrderDesc: !this.state.chffrDrivesOrderDesc });
|
||||
} else {
|
||||
this.setState({ chffrDrivesOrderDesc: true });
|
||||
this.setState({ chffrDrivesSortBy: key });
|
||||
}
|
||||
}
|
||||
|
||||
attemptPandaConnection() {
|
||||
if (!this.state.webUsbEnabled) { return };
|
||||
this.props.handlePandaConnect();
|
||||
openChffrDrive(route) {
|
||||
window.location.search = `?route=${route.fullname}`;
|
||||
}
|
||||
|
||||
renderPandaEligibility() {
|
||||
const { webUsbEnabled, pandaConnected } = this.state;
|
||||
const { attemptingPandaConnection } = this.props;
|
||||
if (!webUsbEnabled) {
|
||||
return (
|
||||
<p>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<a onClick={this.toggleUsbInstructions}>
|
||||
<span>WebUSB is not enabled in your Chrome settings</span>
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
} else if (!pandaConnected && attemptingPandaConnection) {
|
||||
return (
|
||||
<p>
|
||||
<i className="fa fa-spinner animate-spin" />
|
||||
<span className="animate-pulse-opacity">
|
||||
Waiting for panda USB connection
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
toggleUsbInstructions() {
|
||||
this.setState({ viewingUsbInstructions: !this.state.viewingUsbInstructions });
|
||||
}
|
||||
|
||||
navigateToAuth() {
|
||||
const authUrl = auth.authUrl();
|
||||
window.location.href = authUrl;
|
||||
}
|
||||
|
||||
filterRoutesWithCan(drive) {
|
||||
return drive.can === true;
|
||||
}
|
||||
|
||||
handleSearchDrives(drive) {
|
||||
const { chffrDrivesSearch } = this.state;
|
||||
const searchKeywords = chffrDrivesSearch.split(" ")
|
||||
.filter((s) => s.length > 0)
|
||||
.map((s) => s.toLowerCase());
|
||||
|
||||
return searchKeywords.length === 0 ||
|
||||
searchKeywords.some((kw) => (
|
||||
drive.end_geocode.toLowerCase().indexOf(kw) !== -1
|
||||
|| drive.start_geocode.toLowerCase().indexOf(kw) !== -1
|
||||
|| Moment(drive.start_time).format('dddd MMMM Do YYYY').toLowerCase().indexOf(kw) !== -1
|
||||
|| Moment(drive.end_time).format('dddd MMMM Do YYYY').toLowerCase().indexOf(kw) !== -1));
|
||||
}
|
||||
|
||||
handleSortDrives(key) {
|
||||
if (this.state.chffrDrivesSortBy === key) {
|
||||
this.setState({ chffrDrivesOrderDesc: !this.state.chffrDrivesOrderDesc });
|
||||
} else {
|
||||
this.setState({ chffrDrivesOrderDesc: true });
|
||||
this.setState({ chffrDrivesSortBy: key });
|
||||
}
|
||||
}
|
||||
|
||||
openChffrDrive(route) {
|
||||
window.location.search = `?route=${ route.fullname }`;
|
||||
}
|
||||
|
||||
renderPandaEligibility() {
|
||||
const { webUsbEnabled, pandaConnected } = this.state;
|
||||
const { attemptingPandaConnection } = this.props;
|
||||
if (!webUsbEnabled) {
|
||||
return (
|
||||
<p>
|
||||
<i className='fa fa-exclamation-triangle'></i>
|
||||
<a onClick={ this.toggleUsbInstructions }>
|
||||
<span>WebUSB is not enabled in your Chrome settings</span>
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
else if (!pandaConnected && attemptingPandaConnection) {
|
||||
return (
|
||||
<p>
|
||||
<i className='fa fa-spinner animate-spin'></i>
|
||||
<span className='animate-pulse-opacity'>Waiting for panda USB connection</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
renderChffrOption() {
|
||||
const { routes } = this.props;
|
||||
if (routes.length > 0) {
|
||||
return (
|
||||
<div className='cabana-onboarding-mode-chffr'>
|
||||
<div className='cabana-onboarding-mode-chffr-search'>
|
||||
<div className='form-field--small'>
|
||||
<input type='text'
|
||||
id='chffr_drives_search'
|
||||
placeholder='Search chffr drives'
|
||||
value={ this.state.chffrDrivesSearch }
|
||||
onChange={ (e) =>
|
||||
this.setState({ chffrDrivesSearch: e.target.value }) } />
|
||||
<div className='cabana-onboarding-mode-chffr-search-helper'>
|
||||
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ cx('cabana-onboarding-mode-chffr-header', {
|
||||
'is-ordered-desc': this.state.chffrDrivesOrderDesc,
|
||||
'is-ordered-asc': !this.state.chffrDrivesOrderDesc
|
||||
}) }>
|
||||
<div className={ cx('cabana-onboarding-mode-chffr-drive-date', {
|
||||
'is-sorted': this.state.chffrDrivesSortBy === 'start_time',
|
||||
}) }
|
||||
onClick={ () => this.handleSortDrives('start_time') }>
|
||||
<span>Date</span>
|
||||
</div>
|
||||
<div className={ cx('cabana-onboarding-mode-chffr-drive-places', {
|
||||
'is-sorted': this.state.chffrDrivesSortBy === 'end_geocode'
|
||||
}) }
|
||||
onClick={ () => this.handleSortDrives('end_geocode') }>
|
||||
<span>Places</span>
|
||||
</div>
|
||||
<div className={ cx('cabana-onboarding-mode-chffr-drive-time') }>
|
||||
<span>Time</span>
|
||||
</div>
|
||||
<div className={ cx('cabana-onboarding-mode-chffr-drive-distance', {
|
||||
'is-sorted': this.state.chffrDrivesSortBy === 'len'
|
||||
}) }
|
||||
onClick={ () => this.handleSortDrives('len') }>
|
||||
<span>Distance</span>
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-action'></div>
|
||||
</div>
|
||||
<ul className='cabana-onboarding-mode-chffr-drives'>
|
||||
{ _.orderBy(routes, [this.state.chffrDrivesSortBy], [this.state.chffrDrivesOrderDesc ? 'desc' : 'asc'])
|
||||
.filter(this.filterRoutesWithCan)
|
||||
.filter(this.handleSearchDrives)
|
||||
.map((route) => {
|
||||
const routeDuration = Moment.duration(route.end_time.diff(route.start_time));
|
||||
const routeStartClock = Moment(route.start_time).format('LT');
|
||||
const routeEndClock = Moment(route.end_time).format('LT');
|
||||
return (
|
||||
<li key={ route.fullname }
|
||||
className='cabana-onboarding-mode-chffr-drive'>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-date'>
|
||||
<strong>{ Moment(route.start_time._i).format('MMM Do') }</strong>
|
||||
<span>{ Moment(route.start_time._i).format('dddd') }</span>
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-places'>
|
||||
<strong>{ route.end_geocode }</strong>
|
||||
<span>From { route.start_geocode }</span>
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-time'>
|
||||
<strong>
|
||||
{ routeDuration.hours > 0 ? `${ routeDuration._data.hours } hr ` : null }
|
||||
{ `${ routeDuration._data.minutes } min ${ routeDuration._data.seconds } sec` }
|
||||
</strong>
|
||||
<span>{ `${ routeStartClock } - ${ routeEndClock }`}</span>
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-distance'>
|
||||
<strong>{ route.len.toFixed(2) } mi</strong>
|
||||
<span>{ (route.len * 1.6).toFixed(2) } km</span>
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode-chffr-drive-action'>
|
||||
<button className='button--primary'
|
||||
onClick={ () => this.openChffrDrive(route) }>
|
||||
<span>View Drive</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<button onClick={ this.navigateToAuth }
|
||||
className='button--primary button--kiosk'>
|
||||
<i className='fa fa-video-camera'></i>
|
||||
<strong>Log in to View Recorded Drives</strong>
|
||||
<sup>Analyze your car driving data from <em>chffr</em></sup>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
renderOnboardingOptions() {
|
||||
return (
|
||||
<div className='cabana-onboarding-modes'>
|
||||
<div className='cabana-onboarding-mode'>
|
||||
{ this.renderChffrOption() }
|
||||
</div>
|
||||
<div className='cabana-onboarding-mode'>
|
||||
<button className={ cx('button--secondary button--kiosk', {
|
||||
'is-disabled': !this.state.webUsbEnabled || this.props.attemptingPandaConnection
|
||||
}) }
|
||||
onClick={ this.attemptPandaConnection }>
|
||||
<i className='fa fa-bolt'></i>
|
||||
<strong>Launch Realtime Streaming</strong>
|
||||
<sup>Interactively stream car data over USB with <em>panda</em></sup>
|
||||
{ this.renderPandaEligibility() }
|
||||
</button>
|
||||
</div>
|
||||
renderChffrOption() {
|
||||
const { routes } = this.props;
|
||||
if (routes.length > 0) {
|
||||
return (
|
||||
<div className="cabana-onboarding-mode-chffr">
|
||||
<div className="cabana-onboarding-mode-chffr-search">
|
||||
<div className="form-field--small">
|
||||
<input
|
||||
type="text"
|
||||
id="chffr_drives_search"
|
||||
placeholder="Search chffr drives"
|
||||
value={this.state.chffrDrivesSearch}
|
||||
onChange={e =>
|
||||
this.setState({ chffrDrivesSearch: e.target.value })
|
||||
}
|
||||
/>
|
||||
<div className="cabana-onboarding-mode-chffr-search-helper">
|
||||
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderUsbInstructions() {
|
||||
return (
|
||||
<div className='cabana-onboarding-instructions'>
|
||||
<button className='button--small button--inverted' onClick={ this.toggleUsbInstructions }>
|
||||
<i className='fa fa-chevron-left'></i>
|
||||
<span> Go back</span>
|
||||
</button>
|
||||
<h3>Follow these directions to enable WebUSB:</h3>
|
||||
<ol className='cabana-onboarding-instructions-list list--bubbled'>
|
||||
<li>
|
||||
<p><strong>Open your Chrome settings:</strong></p>
|
||||
<div className='inset'>
|
||||
<span>chrome://flags/#enable-experimental-web-platform-features</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Enable Experimental Platform features:</strong></p>
|
||||
<img alt={"Screenshot of Google Chrome Experimental Platform features"}
|
||||
src={ OnboardingModal.instructionalImages.step2 } />
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Enable WebUSB:</strong></p>
|
||||
<img alt={"Screenshot of Google Chrome enable WebUSB"}
|
||||
src={ OnboardingModal.instructionalImages.step3 } />
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Relaunch your Chrome browser and try enabling live mode again.</strong></p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-header", {
|
||||
"is-ordered-desc": this.state.chffrDrivesOrderDesc,
|
||||
"is-ordered-asc": !this.state.chffrDrivesOrderDesc
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-date", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "start_time"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("start_time")}
|
||||
>
|
||||
<span>Date</span>
|
||||
</div>
|
||||
)
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-places", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "end_geocode"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("end_geocode")}
|
||||
>
|
||||
<span>Places</span>
|
||||
</div>
|
||||
<div className={cx("cabana-onboarding-mode-chffr-drive-time")}>
|
||||
<span>Time</span>
|
||||
</div>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-distance", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "len"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("len")}
|
||||
>
|
||||
<span>Distance</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-action" />
|
||||
</div>
|
||||
<ul className="cabana-onboarding-mode-chffr-drives">
|
||||
{_.orderBy(
|
||||
routes,
|
||||
[this.state.chffrDrivesSortBy],
|
||||
[this.state.chffrDrivesOrderDesc ? "desc" : "asc"]
|
||||
)
|
||||
.filter(this.filterRoutesWithCan)
|
||||
.filter(this.handleSearchDrives)
|
||||
.map(route => {
|
||||
const routeDuration = Moment.duration(
|
||||
route.end_time.diff(route.start_time)
|
||||
);
|
||||
const routeStartClock = Moment(route.start_time).format("LT");
|
||||
const routeEndClock = Moment(route.end_time).format("LT");
|
||||
return (
|
||||
<li
|
||||
key={route.fullname}
|
||||
className="cabana-onboarding-mode-chffr-drive"
|
||||
>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-date">
|
||||
<strong>
|
||||
{Moment(route.start_time._i).format("MMM Do")}
|
||||
</strong>
|
||||
<span>{Moment(route.start_time._i).format("dddd")}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-places">
|
||||
<strong>{route.end_geocode}</strong>
|
||||
<span>From {route.start_geocode}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-time">
|
||||
<strong>
|
||||
{routeDuration.hours > 0
|
||||
? `${routeDuration._data.hours} hr `
|
||||
: null}
|
||||
{`${routeDuration._data.minutes} min ${
|
||||
routeDuration._data.seconds
|
||||
} sec`}
|
||||
</strong>
|
||||
<span>{`${routeStartClock} - ${routeEndClock}`}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-distance">
|
||||
<strong>{route.len.toFixed(2)} mi</strong>
|
||||
<span>{(route.len * 1.6).toFixed(2)} km</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-action">
|
||||
<button
|
||||
className="button--primary"
|
||||
onClick={() => this.openChffrDrive(route)}
|
||||
>
|
||||
<span>View Drive</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button
|
||||
onClick={this.navigateToAuth}
|
||||
className="button--primary button--kiosk"
|
||||
>
|
||||
<i className="fa fa-video-camera" />
|
||||
<strong>Log in to View Recorded Drives</strong>
|
||||
<sup>
|
||||
Analyze your car driving data from <em>chffr</em>
|
||||
</sup>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderModalContent() {
|
||||
if (this.state.viewingUsbInstructions) {
|
||||
return this.renderUsbInstructions();
|
||||
}
|
||||
else {
|
||||
return this.renderOnboardingOptions();
|
||||
}
|
||||
}
|
||||
renderOnboardingOptions() {
|
||||
return (
|
||||
<div className="cabana-onboarding-modes">
|
||||
<div className="cabana-onboarding-mode">{this.renderChffrOption()}</div>
|
||||
<div className="cabana-onboarding-mode">
|
||||
<button
|
||||
className={cx("button--secondary button--kiosk", {
|
||||
"is-disabled":
|
||||
!this.state.webUsbEnabled ||
|
||||
this.props.attemptingPandaConnection
|
||||
})}
|
||||
onClick={this.attemptPandaConnection}
|
||||
>
|
||||
<i className="fa fa-bolt" />
|
||||
<strong>Launch Realtime Streaming</strong>
|
||||
<sup>
|
||||
Interactively stream car data over USB with <em>panda</em>
|
||||
</sup>
|
||||
{this.renderPandaEligibility()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderModalFooter() {
|
||||
return (
|
||||
renderUsbInstructions() {
|
||||
return (
|
||||
<div className="cabana-onboarding-instructions">
|
||||
<button
|
||||
className="button--small button--inverted"
|
||||
onClick={this.toggleUsbInstructions}
|
||||
>
|
||||
<i className="fa fa-chevron-left" />
|
||||
<span> Go back</span>
|
||||
</button>
|
||||
<h3>Follow these directions to enable WebUSB:</h3>
|
||||
<ol className="cabana-onboarding-instructions-list list--bubbled">
|
||||
<li>
|
||||
<p>
|
||||
<span>Don't have a <a href='https://panda.comma.ai' target='_blank'>panda</a>? </span>
|
||||
<span><a href='https://panda.comma.ai' target='_blank'>Get one here</a> </span>
|
||||
<span>or <a href='https://community.comma.ai/cabana/?demo=1'>try the demo</a>.</span>
|
||||
<strong>Open your Chrome settings:</strong>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<div className="inset">
|
||||
<span>
|
||||
chrome://flags/#enable-experimental-web-platform-features
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Enable Experimental Platform features:</strong>
|
||||
</p>
|
||||
<img
|
||||
alt={"Screenshot of Google Chrome Experimental Platform features"}
|
||||
src={OnboardingModal.instructionalImages.step2}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Enable WebUSB:</strong>
|
||||
</p>
|
||||
<img
|
||||
alt={"Screenshot of Google Chrome enable WebUSB"}
|
||||
src={OnboardingModal.instructionalImages.step3}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>
|
||||
Relaunch your Chrome browser and try enabling live mode again.
|
||||
</strong>
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title='Welcome to cabana'
|
||||
subtitle='Get started by viewing your chffr drives or enabling live mode'
|
||||
footer={ this.renderModalFooter() }
|
||||
disableClose={ true }
|
||||
variations={['wide', 'dark']}>
|
||||
{ this.renderModalContent() }
|
||||
</Modal>
|
||||
);
|
||||
renderModalContent() {
|
||||
if (this.state.viewingUsbInstructions) {
|
||||
return this.renderUsbInstructions();
|
||||
} else {
|
||||
return this.renderOnboardingOptions();
|
||||
}
|
||||
}
|
||||
|
||||
renderModalFooter() {
|
||||
return (
|
||||
<p>
|
||||
<span>
|
||||
Don't have a{" "}
|
||||
<a href="https://panda.comma.ai" target="_blank">
|
||||
panda
|
||||
</a>?{" "}
|
||||
</span>
|
||||
<span>
|
||||
<a href="https://panda.comma.ai" target="_blank">
|
||||
Get one here
|
||||
</a>{" "}
|
||||
</span>
|
||||
<span>
|
||||
or{" "}
|
||||
<a href="https://community.comma.ai/cabana/?demo=1">try the demo</a>.
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title="Welcome to cabana"
|
||||
subtitle="Get started by viewing your chffr drives or enabling live mode"
|
||||
footer={this.renderModalFooter()}
|
||||
disableClose={true}
|
||||
variations={["wide", "dark"]}
|
||||
>
|
||||
{this.renderModalContent()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +1,99 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import Measure from 'react-measure';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
import Measure from "react-measure";
|
||||
|
||||
export default class Modal extends Component {
|
||||
static PropTypes = {
|
||||
variations: PropTypes.array,
|
||||
disableClose: PropTypes.bool,
|
||||
handleClose: PropTypes.func,
|
||||
handleSave: PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
navigation: PropTypes.node,
|
||||
actions: PropTypes.node,
|
||||
footer: PropTypes.node,
|
||||
static PropTypes = {
|
||||
variations: PropTypes.array,
|
||||
disableClose: PropTypes.bool,
|
||||
handleClose: PropTypes.func,
|
||||
handleSave: PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
navigation: PropTypes.node,
|
||||
actions: PropTypes.node,
|
||||
footer: PropTypes.node
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
windowHeight: {},
|
||||
modalHeight: {}
|
||||
};
|
||||
|
||||
this.updateHeights = this.updateHeights.bind(this);
|
||||
}
|
||||
|
||||
updateHeights(contentRect) {
|
||||
this.setState({ windowHeight: window.innerHeight });
|
||||
this.setState({ modalHeight: contentRect.bounds.height });
|
||||
}
|
||||
|
||||
readVariationClasses() {
|
||||
if (this.props.variations) {
|
||||
const { variations } = this.props;
|
||||
const classes = variations.reduce(
|
||||
(classes, variation) => classes + `cabana-modal--${variation} `,
|
||||
""
|
||||
);
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
checkClosability() {
|
||||
return this.props.disableClose || false;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
windowHeight: {},
|
||||
modalHeight: {},
|
||||
};
|
||||
checkYScrollability() {
|
||||
return this.state.modalHeight > this.state.windowHeight;
|
||||
}
|
||||
|
||||
this.updateHeights = this.updateHeights.bind(this);
|
||||
}
|
||||
|
||||
updateHeights(contentRect) {
|
||||
this.setState({ windowHeight: window.innerHeight });
|
||||
this.setState({ modalHeight: contentRect.bounds.height });
|
||||
}
|
||||
|
||||
readVariationClasses() {
|
||||
if (this.props.variations) {
|
||||
const { variations } = this.props;
|
||||
const classes = variations.reduce((classes, variation) =>
|
||||
classes + `cabana-modal--${variation} `,
|
||||
'');
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
checkClosability() {
|
||||
return (this.props.disableClose || false);
|
||||
}
|
||||
|
||||
checkYScrollability() {
|
||||
return (this.state.modalHeight > this.state.windowHeight);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ cx(
|
||||
'cabana-modal',
|
||||
this.readVariationClasses(), {
|
||||
'cabana-modal--not-closable': this.checkClosability(),
|
||||
'cabana-modal--scrollable-y': this.checkYScrollability(),
|
||||
}) }>
|
||||
<Measure
|
||||
bounds
|
||||
onResize={(contentRect) => {
|
||||
this.updateHeights(contentRect);
|
||||
}}>
|
||||
{({ measureRef }) =>
|
||||
<div ref={ measureRef } className='cabana-modal-container'>
|
||||
<div className='cabana-modal-close-icon'
|
||||
onClick={ this.props.handleClose }></div>
|
||||
<div className='cabana-modal-header'>
|
||||
<h1>{ this.props.title }</h1>
|
||||
<p>{ this.props.subtitle }</p>
|
||||
</div>
|
||||
<div className='cabana-modal-navigation'>
|
||||
{ this.props.navigation }
|
||||
</div>
|
||||
<div className='cabana-modal-body'>
|
||||
<div className='cabana-modal-body-window'>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
<div className='cabana-modal-body-gradient'></div>
|
||||
</div>
|
||||
<div className='cabana-modal-actions'>
|
||||
{ this.props.actions }
|
||||
</div>
|
||||
<div className='cabana-modal-footer'>
|
||||
{ this.props.footer }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Measure>
|
||||
<div className='cabana-modal-backdrop'
|
||||
onClick={ this.props.handleClose }>
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cx("cabana-modal", this.readVariationClasses(), {
|
||||
"cabana-modal--not-closable": this.checkClosability(),
|
||||
"cabana-modal--scrollable-y": this.checkYScrollability()
|
||||
})}
|
||||
>
|
||||
<Measure
|
||||
bounds
|
||||
onResize={contentRect => {
|
||||
this.updateHeights(contentRect);
|
||||
}}
|
||||
>
|
||||
{({ measureRef }) => (
|
||||
<div ref={measureRef} className="cabana-modal-container">
|
||||
<div
|
||||
className="cabana-modal-close-icon"
|
||||
onClick={this.props.handleClose}
|
||||
/>
|
||||
<div className="cabana-modal-header">
|
||||
<h1>{this.props.title}</h1>
|
||||
<p>{this.props.subtitle}</p>
|
||||
</div>
|
||||
<div className="cabana-modal-navigation">
|
||||
{this.props.navigation}
|
||||
</div>
|
||||
<div className="cabana-modal-body">
|
||||
<div className="cabana-modal-body-window">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div className="cabana-modal-body-gradient" />
|
||||
</div>
|
||||
<div className="cabana-modal-actions">{this.props.actions}</div>
|
||||
<div className="cabana-modal-footer">{this.props.footer}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</Measure>
|
||||
<div
|
||||
className="cabana-modal-backdrop"
|
||||
onClick={this.props.handleClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,128 +1,137 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import {PART_SEGMENT_LENGTH} from '../config';
|
||||
import { PART_SEGMENT_LENGTH } from "../config";
|
||||
|
||||
export default class PartSelector extends Component {
|
||||
static selectorWidth = 150;
|
||||
static propTypes = {
|
||||
onPartChange: PropTypes.func.isRequired,
|
||||
partsCount: PropTypes.number.isRequired,
|
||||
static selectorWidth = 150;
|
||||
static propTypes = {
|
||||
onPartChange: PropTypes.func.isRequired,
|
||||
partsCount: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
|
||||
selectedPart: 0,
|
||||
isDragging: false
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.selectNextPart = this.selectNextPart.bind(this);
|
||||
this.selectPrevPart = this.selectPrevPart.bind(this);
|
||||
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
|
||||
this.onSelectedPartMouseMove = this.onSelectedPartMouseMove.bind(this);
|
||||
this.onSelectedPartDragEnd = this.onSelectedPartDragEnd.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
|
||||
selectedPart: 0,
|
||||
isDragging: false,
|
||||
};
|
||||
makePartStyle(partsCount, selectedPart) {
|
||||
return {
|
||||
left: selectedPart / partsCount * PartSelector.selectorWidth,
|
||||
width: PART_SEGMENT_LENGTH / partsCount * PartSelector.selectorWidth
|
||||
};
|
||||
}
|
||||
|
||||
this.selectNextPart = this.selectNextPart.bind(this);
|
||||
this.selectPrevPart = this.selectPrevPart.bind(this);
|
||||
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
|
||||
this.onSelectedPartMouseMove = this.onSelectedPartMouseMove.bind(this);
|
||||
this.onSelectedPartDragEnd = this.onSelectedPartDragEnd.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.partsCount !== this.props.partsCount) {
|
||||
const selectedPartStyle = this.makePartStyle(
|
||||
nextProps.partsCount,
|
||||
this.state.selectedPart
|
||||
);
|
||||
this.setState({ selectedPartStyle });
|
||||
}
|
||||
}
|
||||
|
||||
selectPart(part) {
|
||||
part = Math.max(
|
||||
0,
|
||||
Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part)
|
||||
);
|
||||
if (part === this.state.selectedPart) {
|
||||
return;
|
||||
}
|
||||
|
||||
makePartStyle(partsCount, selectedPart) {
|
||||
return {
|
||||
left: (selectedPart / partsCount) * PartSelector.selectorWidth,
|
||||
width: (PART_SEGMENT_LENGTH / partsCount) * PartSelector.selectorWidth
|
||||
};
|
||||
this.props.onPartChange(part);
|
||||
this.setState({
|
||||
selectedPart: part,
|
||||
selectedPartStyle: this.makePartStyle(this.props.partsCount, part)
|
||||
});
|
||||
}
|
||||
|
||||
selectNextPart() {
|
||||
let { selectedPart } = this.state;
|
||||
selectedPart++;
|
||||
if (selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.partsCount !== this.props.partsCount) {
|
||||
const selectedPartStyle = this.makePartStyle(nextProps.partsCount, this.state.selectedPart);
|
||||
this.setState({selectedPartStyle});
|
||||
}
|
||||
this.selectPart(selectedPart);
|
||||
}
|
||||
|
||||
selectPrevPart() {
|
||||
let { selectedPart } = this.state;
|
||||
selectedPart--;
|
||||
if (selectedPart < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectPart(part) {
|
||||
part = Math.max(0, Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part));
|
||||
if(part === this.state.selectedPart){
|
||||
return;
|
||||
}
|
||||
this.selectPart(selectedPart);
|
||||
}
|
||||
|
||||
this.props.onPartChange(part);
|
||||
this.setState({selectedPart: part,
|
||||
selectedPartStyle: this.makePartStyle(this.props.partsCount,
|
||||
part)});
|
||||
}
|
||||
|
||||
selectNextPart() {
|
||||
let {selectedPart} = this.state;
|
||||
selectedPart++;
|
||||
if(selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectPart(selectedPart);
|
||||
}
|
||||
|
||||
selectPrevPart() {
|
||||
let {selectedPart} = this.state;
|
||||
selectedPart--;
|
||||
if(selectedPart < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectPart(selectedPart);
|
||||
}
|
||||
|
||||
partAtClientX(clientX) {
|
||||
const rect = this.selectorRect.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
|
||||
}
|
||||
|
||||
onSelectedPartDragStart(e) {
|
||||
this.setState({isDragging: true});
|
||||
document.addEventListener('mouseup', this.onSelectedPartDragEnd);
|
||||
}
|
||||
|
||||
onSelectedPartMouseMove(e) {
|
||||
if(!this.state.isDragging) return;
|
||||
|
||||
const part = this.partAtClientX(e.clientX);
|
||||
this.selectPart(part);
|
||||
}
|
||||
|
||||
onSelectedPartDragEnd(e) {
|
||||
this.setState({isDragging: false});
|
||||
document.removeEventListener('mouseup', this.onSelectedPartDragEnd);
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
const part = this.partAtClientX(e.clientX);
|
||||
this.selectPart(part);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {selectedPartStyle} = this.state;
|
||||
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
|
||||
// all parts are available so no need to render the partselector
|
||||
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className='cabana-explorer-part-selector'>
|
||||
<div className='cabana-explorer-part-selector-track'
|
||||
ref={ (selector) => this.selectorRect = selector }
|
||||
style={{ width: PartSelector.selectorWidth }}
|
||||
onMouseMove={ this.onSelectedPartMouseMove }
|
||||
onClick={ this.onClick }>
|
||||
<div className='cabana-explorer-part-selector-track-active'
|
||||
style={ selectedPartStyle }
|
||||
onMouseDown={ this.onSelectedPartDragStart }
|
||||
onMouseUp={ this.onSelectedPartDragEnd }>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
partAtClientX(clientX) {
|
||||
const rect = this.selectorRect.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
|
||||
}
|
||||
|
||||
onSelectedPartDragStart(e) {
|
||||
this.setState({ isDragging: true });
|
||||
document.addEventListener("mouseup", this.onSelectedPartDragEnd);
|
||||
}
|
||||
|
||||
onSelectedPartMouseMove(e) {
|
||||
if (!this.state.isDragging) return;
|
||||
|
||||
const part = this.partAtClientX(e.clientX);
|
||||
this.selectPart(part);
|
||||
}
|
||||
|
||||
onSelectedPartDragEnd(e) {
|
||||
this.setState({ isDragging: false });
|
||||
document.removeEventListener("mouseup", this.onSelectedPartDragEnd);
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
const part = this.partAtClientX(e.clientX);
|
||||
this.selectPart(part);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedPartStyle } = this.state;
|
||||
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
|
||||
// all parts are available so no need to render the partselector
|
||||
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="cabana-explorer-part-selector">
|
||||
<div
|
||||
className="cabana-explorer-part-selector-track"
|
||||
ref={selector => (this.selectorRect = selector)}
|
||||
style={{ width: PartSelector.selectorWidth }}
|
||||
onMouseMove={this.onSelectedPartMouseMove}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<div
|
||||
className="cabana-explorer-part-selector-track-active"
|
||||
style={selectedPartStyle}
|
||||
onMouseDown={this.onSelectedPartDragStart}
|
||||
onMouseUp={this.onSelectedPartDragEnd}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default class PlayButton extends Component {
|
||||
static propTypes = {
|
||||
|
@ -11,33 +11,41 @@ export default class PlayButton extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {'hover': false};
|
||||
this.state = { hover: false };
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
imageSource() {
|
||||
const {hover} = this.state;
|
||||
const {isPlaying} = this.props;
|
||||
if(isPlaying) {
|
||||
if(hover) {
|
||||
return (process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg");
|
||||
const { hover } = this.state;
|
||||
const { isPlaying } = this.props;
|
||||
if (isPlaying) {
|
||||
if (hover) {
|
||||
return (
|
||||
process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg"
|
||||
);
|
||||
} else {
|
||||
return (process.env.PUBLIC_URL + "/img/ic_pause_circle_outline_white_24px.svg");
|
||||
return (
|
||||
process.env.PUBLIC_URL + "/img/ic_pause_circle_outline_white_24px.svg"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if(hover) {
|
||||
return (process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg");
|
||||
if (hover) {
|
||||
return (
|
||||
process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg"
|
||||
);
|
||||
} else {
|
||||
return (process.env.PUBLIC_URL + "/img/ic_play_circle_outline_white_24px.svg");
|
||||
return (
|
||||
process.env.PUBLIC_URL + "/img/ic_play_circle_outline_white_24px.svg"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
let {isPlaying} = this.props;
|
||||
let { isPlaying } = this.props;
|
||||
|
||||
if(!isPlaying) {
|
||||
if (!isPlaying) {
|
||||
this.props.onPlay();
|
||||
} else {
|
||||
this.props.onPause();
|
||||
|
@ -45,11 +53,15 @@ export default class PlayButton extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <img src={this.imageSource()}
|
||||
alt={this.props.isPlaying ? 'Pause' : 'Play'}
|
||||
className={this.props.className}
|
||||
onClick={this.onClick}
|
||||
onMouseOver={() => this.setState({hover: true})}
|
||||
onMouseLeave={() => this.setState({hover: false})} />;
|
||||
return (
|
||||
<img
|
||||
src={this.imageSource()}
|
||||
alt={this.props.isPlaying ? "Pause" : "Play"}
|
||||
className={this.props.className}
|
||||
onClick={this.onClick}
|
||||
onMouseOver={() => this.setState({ hover: true })}
|
||||
onMouseLeave={() => this.setState({ hover: false })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,220 +1,237 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PlayButton from '../PlayButton';
|
||||
import debounce from '../../utils/debounce';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import PlayButton from "../PlayButton";
|
||||
import debounce from "../../utils/debounce";
|
||||
|
||||
export default 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,
|
||||
playing: PropTypes.bool,
|
||||
segmentProgress: PropTypes.func,
|
||||
ratioTime: PropTypes.func,
|
||||
nearestFrameTime: PropTypes.number
|
||||
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,
|
||||
playing: PropTypes.bool,
|
||||
segmentProgress: PropTypes.func,
|
||||
ratioTime: PropTypes.func,
|
||||
nearestFrameTime: PropTypes.number
|
||||
};
|
||||
|
||||
static hiddenMarkerStyle = { display: "none", left: 0 };
|
||||
static zeroSeekedBarStyle = { width: 0 };
|
||||
static hiddenTooltipStyle = { display: "none", left: 0 };
|
||||
static markerWidth = 20;
|
||||
static tooltipWidth = 50;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
||||
ratio: 0,
|
||||
tooltipTime: "0:00",
|
||||
isPlaying: false,
|
||||
isDragging: false
|
||||
};
|
||||
|
||||
static hiddenMarkerStyle = {display: 'none', left: 0};
|
||||
static zeroSeekedBarStyle = {width: 0};
|
||||
static hiddenTooltipStyle = {display: 'none', left: 0};
|
||||
static markerWidth = 20;
|
||||
static tooltipWidth = 50;
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onMouseLeave = this.onMouseLeave.bind(this);
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onPlay = this.onPlay.bind(this);
|
||||
this.onPause = this.onPause.bind(this);
|
||||
this.executePlayTimer = this.executePlayTimer.bind(this);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
||||
ratio: 0,
|
||||
tooltipTime: '0:00',
|
||||
isPlaying: false,
|
||||
isDragging: false,
|
||||
};
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { ratio } = this.state;
|
||||
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onMouseLeave = this.onMouseLeave.bind(this);
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onPlay = this.onPlay.bind(this);
|
||||
this.onPause = this.onPause.bind(this);
|
||||
this.executePlayTimer = this.executePlayTimer.bind(this);
|
||||
if (
|
||||
JSON.stringify(this.props.segmentIndices) !==
|
||||
JSON.stringify(nextProps.segmentIndices)
|
||||
) {
|
||||
this.setState({
|
||||
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
ratio: 0
|
||||
});
|
||||
} else if (nextProps.secondsLoaded !== this.props.secondsLoaded) {
|
||||
// adjust ratio in line with new secondsLoaded
|
||||
const secondsSeeked = ratio * this.props.secondsLoaded;
|
||||
const newRatio = secondsSeeked / nextProps.secondsLoaded;
|
||||
this.updateSeekedBar(newRatio);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {ratio} = this.state;
|
||||
|
||||
if(JSON.stringify(this.props.segmentIndices)
|
||||
!== JSON.stringify(nextProps.segmentIndices)) {
|
||||
this.setState({seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
ratio: 0});
|
||||
} else if(nextProps.secondsLoaded !== this.props.secondsLoaded) {
|
||||
// adjust ratio in line with new secondsLoaded
|
||||
const secondsSeeked = ratio * this.props.secondsLoaded;
|
||||
const newRatio = secondsSeeked / nextProps.secondsLoaded;
|
||||
this.updateSeekedBar(newRatio);
|
||||
}
|
||||
|
||||
if(this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
|
||||
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
|
||||
this.updateSeekedBar(newRatio);
|
||||
}
|
||||
|
||||
if(nextProps.playing && !this.state.isPlaying) {
|
||||
this.onPlay();
|
||||
} else if(!nextProps.playing && this.state.isPlaying) {
|
||||
this.onPause();
|
||||
}
|
||||
if (this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
|
||||
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
|
||||
this.updateSeekedBar(newRatio);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.cancelAnimationFrame(this.playTimer);
|
||||
if (nextProps.playing && !this.state.isPlaying) {
|
||||
this.onPlay();
|
||||
} else if (!nextProps.playing && this.state.isPlaying) {
|
||||
this.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.cancelAnimationFrame(this.playTimer);
|
||||
}
|
||||
|
||||
mouseEventXOffsetPercent(e) {
|
||||
const rect = this.progressBar.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
|
||||
return 100 * (x / this.progressBar.offsetWidth);
|
||||
}
|
||||
|
||||
updateDraggingSeek = debounce(ratio => this.props.onUserSeek(ratio), 250);
|
||||
|
||||
onMouseMove(e) {
|
||||
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
|
||||
if (markerOffsetPct < 0) {
|
||||
this.onMouseLeave();
|
||||
return;
|
||||
}
|
||||
const markerWidth = RouteSeeker.markerWidth;
|
||||
|
||||
const markerLeft = `calc(${markerOffsetPct + "%"} - ${markerWidth / 2}px)`;
|
||||
const markerStyle = {
|
||||
display: "",
|
||||
left: markerLeft
|
||||
};
|
||||
const tooltipWidth = RouteSeeker.tooltipWidth;
|
||||
const tooltipLeft = `calc(${markerOffsetPct + "%"} - ${tooltipWidth /
|
||||
2}px)`;
|
||||
|
||||
const tooltipStyle = { display: "flex", left: tooltipLeft };
|
||||
const ratio = Math.max(0, markerOffsetPct / 100);
|
||||
if (this.state.isDragging) {
|
||||
this.updateSeekedBar(ratio);
|
||||
this.updateDraggingSeek(ratio);
|
||||
}
|
||||
|
||||
mouseEventXOffsetPercent(e) {
|
||||
const rect = this.progressBar.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
this.setState({
|
||||
markerStyle,
|
||||
tooltipStyle,
|
||||
tooltipTime: this.props.ratioTime(ratio).toFixed(3)
|
||||
});
|
||||
}
|
||||
|
||||
return 100 * (x / this.progressBar.offsetWidth);
|
||||
onMouseLeave(e) {
|
||||
this.setState({
|
||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
||||
isDragging: false
|
||||
});
|
||||
}
|
||||
|
||||
updateSeekedBar(ratio) {
|
||||
const seekedBarStyle = { width: 100 * ratio + "%" };
|
||||
this.setState({ seekedBarStyle, ratio });
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
let ratio = this.mouseEventXOffsetPercent(e) / 100;
|
||||
ratio = Math.min(1, Math.max(0, ratio));
|
||||
this.updateSeekedBar(ratio);
|
||||
this.props.onUserSeek(ratio);
|
||||
}
|
||||
|
||||
onPlay() {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
let { ratio } = this.state;
|
||||
if (ratio >= 1) {
|
||||
ratio = 0;
|
||||
}
|
||||
this.setState({ isPlaying: true, ratio });
|
||||
this.props.onPlay();
|
||||
}
|
||||
|
||||
executePlayTimer() {
|
||||
const { videoElement } = this.props;
|
||||
if (videoElement === null) {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
updateDraggingSeek = debounce((ratio) => this.props.onUserSeek(ratio), 250);
|
||||
const { currentTime } = videoElement;
|
||||
let newRatio = this.props.segmentProgress(currentTime);
|
||||
|
||||
onMouseMove(e) {
|
||||
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
|
||||
if(markerOffsetPct < 0) {
|
||||
this.onMouseLeave();
|
||||
return;
|
||||
}
|
||||
const markerWidth = RouteSeeker.markerWidth;
|
||||
|
||||
const markerLeft = `calc(${markerOffsetPct + '%'} - ${markerWidth / 2}px)`;
|
||||
const markerStyle = {
|
||||
display: '',
|
||||
left: markerLeft
|
||||
};
|
||||
const tooltipWidth = RouteSeeker.tooltipWidth;
|
||||
const tooltipLeft = `calc(${markerOffsetPct + '%'} - ${tooltipWidth / 2}px)`;
|
||||
|
||||
const tooltipStyle = { display: 'flex', left: tooltipLeft };
|
||||
const ratio = Math.max(0, markerOffsetPct / 100);
|
||||
if(this.state.isDragging) {
|
||||
this.updateSeekedBar(ratio);
|
||||
this.updateDraggingSeek(ratio);
|
||||
}
|
||||
|
||||
this.setState({markerStyle,
|
||||
tooltipStyle,
|
||||
tooltipTime: this.props.ratioTime(ratio).toFixed(3)});
|
||||
if (newRatio === this.state.ratio) {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
onMouseLeave(e) {
|
||||
this.setState({markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
||||
isDragging: false});
|
||||
if (newRatio >= 1) {
|
||||
newRatio = 0;
|
||||
this.props.onUserSeek(newRatio);
|
||||
}
|
||||
|
||||
updateSeekedBar(ratio) {
|
||||
const seekedBarStyle = { width: (100 * ratio) + '%' };
|
||||
this.setState({seekedBarStyle, ratio})
|
||||
if (newRatio >= 0) {
|
||||
this.updateSeekedBar(newRatio);
|
||||
this.props.onPlaySeek(currentTime);
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
let ratio = this.mouseEventXOffsetPercent(e) / 100;
|
||||
ratio = Math.min(1, Math.max(0, ratio));
|
||||
this.updateSeekedBar(ratio);
|
||||
this.props.onUserSeek(ratio);
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
}
|
||||
|
||||
onPause() {
|
||||
window.cancelAnimationFrame(this.playTimer);
|
||||
this.setState({ isPlaying: false });
|
||||
this.props.onPause();
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
if (!this.state.isDragging) {
|
||||
this.setState({ isDragging: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onPlay() {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
let {ratio} = this.state;
|
||||
if(ratio >= 1) {
|
||||
ratio = 0;
|
||||
}
|
||||
this.setState({isPlaying: true, ratio});
|
||||
this.props.onPlay();
|
||||
onMouseUp() {
|
||||
if (this.state.isDragging) {
|
||||
this.setState({ isDragging: false });
|
||||
}
|
||||
}
|
||||
|
||||
executePlayTimer() {
|
||||
const {videoElement} = this.props;
|
||||
if(videoElement === null) {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
const {currentTime} = videoElement;
|
||||
let newRatio = this.props.segmentProgress(currentTime);
|
||||
|
||||
if(newRatio === this.state.ratio) {
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
if(newRatio >= 1) {
|
||||
newRatio = 0;
|
||||
this.props.onUserSeek(newRatio);
|
||||
}
|
||||
|
||||
if(newRatio >= 0) {
|
||||
this.updateSeekedBar(newRatio);
|
||||
this.props.onPlaySeek(currentTime);
|
||||
}
|
||||
|
||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||
}
|
||||
|
||||
onPause() {
|
||||
window.cancelAnimationFrame(this.playTimer);
|
||||
this.setState({isPlaying: false});
|
||||
this.props.onPause();
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
if(!this.state.isDragging) {
|
||||
this.setState({isDragging: true});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
if(this.state.isDragging) {
|
||||
this.setState({isDragging: false});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {seekedBarStyle, markerStyle, tooltipStyle} = this.state;
|
||||
return (
|
||||
<div className='cabana-explorer-visuals-camera-seeker'>
|
||||
<PlayButton
|
||||
className={'cabana-explorer-visuals-camera-seeker-playbutton'}
|
||||
onPlay={this.onPlay}
|
||||
onPause={this.onPause}
|
||||
isPlaying={this.state.isPlaying} />
|
||||
<div className={'cabana-explorer-visuals-camera-seeker-progress'}
|
||||
onMouseMove={this.onMouseMove}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onMouseUp={this.onMouseUp}
|
||||
onClick={this.onClick}
|
||||
ref={(ref) => this.progressBar = ref}>
|
||||
<div className={'cabana-explorer-visuals-camera-seeker-progress-tooltip'}
|
||||
style={tooltipStyle}>
|
||||
{this.state.tooltipTime}
|
||||
</div>
|
||||
<div className={'cabana-explorer-visuals-camera-seeker-progress-marker'}
|
||||
style={markerStyle}></div>
|
||||
<div className={'cabana-explorer-visuals-camera-seeker-progress-inner'}
|
||||
style={seekedBarStyle}></div>
|
||||
</div>
|
||||
render() {
|
||||
const { seekedBarStyle, markerStyle, tooltipStyle } = this.state;
|
||||
return (
|
||||
<div className="cabana-explorer-visuals-camera-seeker">
|
||||
<PlayButton
|
||||
className={"cabana-explorer-visuals-camera-seeker-playbutton"}
|
||||
onPlay={this.onPlay}
|
||||
onPause={this.onPause}
|
||||
isPlaying={this.state.isPlaying}
|
||||
/>
|
||||
<div
|
||||
className={"cabana-explorer-visuals-camera-seeker-progress"}
|
||||
onMouseMove={this.onMouseMove}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onMouseUp={this.onMouseUp}
|
||||
onClick={this.onClick}
|
||||
ref={ref => (this.progressBar = ref)}
|
||||
>
|
||||
<div
|
||||
className={"cabana-explorer-visuals-camera-seeker-progress-tooltip"}
|
||||
style={tooltipStyle}
|
||||
>
|
||||
{this.state.tooltipTime}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div
|
||||
className={"cabana-explorer-visuals-camera-seeker-progress-marker"}
|
||||
style={markerStyle}
|
||||
/>
|
||||
<div
|
||||
className={"cabana-explorer-visuals-camera-seeker-progress-inner"}
|
||||
style={seekedBarStyle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import RouteSeeker from './RouteSeeker';
|
||||
export default RouteSeeker;
|
||||
import RouteSeeker from "./RouteSeeker";
|
||||
export default RouteSeeker;
|
||||
|
|
|
@ -1,194 +1,205 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { StyleSheet, css } from "aphrodite/no-important";
|
||||
|
||||
import HLS from './HLS';
|
||||
import {cameraPath} from '../api/routes';
|
||||
import Video from '../api/video';
|
||||
import RouteSeeker from './RouteSeeker/RouteSeeker';
|
||||
import HLS from "./HLS";
|
||||
import { cameraPath } from "../api/routes";
|
||||
import Video from "../api/video";
|
||||
import RouteSeeker from "./RouteSeeker/RouteSeeker";
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
loadingOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 3
|
||||
},
|
||||
loadingSpinner: {
|
||||
width: '25%',
|
||||
height: '25%',
|
||||
display: 'block'
|
||||
},
|
||||
img: {
|
||||
height: 480,
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
zIndex: 2
|
||||
},
|
||||
hls: {
|
||||
zIndex: 1,
|
||||
height: 480,
|
||||
backgroundColor: 'rgba(0,0,0,0.9)'
|
||||
},
|
||||
seekBar: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
zIndex: 4
|
||||
}
|
||||
loadingOverlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 3
|
||||
},
|
||||
loadingSpinner: {
|
||||
width: "25%",
|
||||
height: "25%",
|
||||
display: "block"
|
||||
},
|
||||
img: {
|
||||
height: 480,
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
zIndex: 2
|
||||
},
|
||||
hls: {
|
||||
zIndex: 1,
|
||||
height: 480,
|
||||
backgroundColor: "rgba(0,0,0,0.9)"
|
||||
},
|
||||
seekBar: {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
zIndex: 4
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default class RouteVideoSync extends Component {
|
||||
static propTypes = {
|
||||
userSeekIndex: PropTypes.number.isRequired,
|
||||
secondsLoaded: PropTypes.number.isRequired,
|
||||
startOffset: PropTypes.number.isRequired,
|
||||
message: PropTypes.object,
|
||||
firstCanTime: PropTypes.number.isRequired,
|
||||
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
|
||||
static propTypes = {
|
||||
userSeekIndex: PropTypes.number.isRequired,
|
||||
secondsLoaded: PropTypes.number.isRequired,
|
||||
startOffset: PropTypes.number.isRequired,
|
||||
message: PropTypes.object,
|
||||
firstCanTime: PropTypes.number.isRequired,
|
||||
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
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
shouldShowJpeg: true,
|
||||
isLoading: true,
|
||||
videoElement: null,
|
||||
shouldRestartHls: false
|
||||
};
|
||||
|
||||
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);
|
||||
this.onHlsRestart = this.onHlsRestart.bind(this);
|
||||
this.ratioTime = this.ratioTime.bind(this);
|
||||
}
|
||||
|
||||
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);
|
||||
this.onHlsRestart = this.onHlsRestart.bind(this);
|
||||
this.ratioTime = this.ratioTime.bind(this);
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
this.props.userSeekIndex !== nextProps.userSeekIndex ||
|
||||
this.props.canFrameOffset !== nextProps.canFrameOffset ||
|
||||
(this.props.message &&
|
||||
nextProps.message &&
|
||||
this.props.message.entries.length !== nextProps.message.entries.length)
|
||||
) {
|
||||
this.setState({ shouldRestartHls: true });
|
||||
}
|
||||
}
|
||||
|
||||
nearestFrameUrl() {
|
||||
const { url } = this.props;
|
||||
const sec = Math.round(this.props.userSeekTime);
|
||||
return cameraPath(url, sec);
|
||||
}
|
||||
|
||||
loadingOverlay() {
|
||||
return (
|
||||
<div className={css(Styles.loadingOverlay)}>
|
||||
<img
|
||||
className={css(Styles.loadingSpinner)}
|
||||
src={process.env.PUBLIC_URL + "/img/loading.svg"}
|
||||
alt={"Loading video"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.props.userSeekIndex !== nextProps.userSeekIndex
|
||||
|| this.props.canFrameOffset !== nextProps.canFrameOffset
|
||||
|| (this.props.message
|
||||
&& nextProps.message
|
||||
&& this.props.message.entries.length !== nextProps.message.entries.length)) {
|
||||
this.setState({shouldRestartHls: true});
|
||||
}
|
||||
const ratio =
|
||||
(currentTime - this.props.startOffset) / this.props.secondsLoaded;
|
||||
return Math.max(0, Math.min(1, ratio));
|
||||
}
|
||||
|
||||
ratioTime(ratio) {
|
||||
return ratio * this.props.secondsLoaded + this.props.startOffset;
|
||||
}
|
||||
|
||||
onVideoElementAvailable(videoElement) {
|
||||
this.setState({ videoElement });
|
||||
}
|
||||
|
||||
onUserSeek(ratio) {
|
||||
/* ratio in [0,1] */
|
||||
|
||||
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
|
||||
if (ratio === 0) {
|
||||
this.setState({ shouldRestartHls: true }, funcSeekToRatio);
|
||||
} else {
|
||||
funcSeekToRatio();
|
||||
}
|
||||
}
|
||||
|
||||
nearestFrameUrl() {
|
||||
const {url} = this.props;
|
||||
const sec = Math.round(this.props.userSeekTime);
|
||||
return cameraPath(url, sec);
|
||||
}
|
||||
onHlsRestart() {
|
||||
this.setState({ shouldRestartHls: false });
|
||||
}
|
||||
|
||||
loadingOverlay() {
|
||||
return (<div className={css(Styles.loadingOverlay)}>
|
||||
<img className={css(Styles.loadingSpinner)}
|
||||
src={process.env.PUBLIC_URL + "/img/loading.svg"}
|
||||
alt={"Loading video"}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
|
||||
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;
|
||||
return Math.max(0, Math.min(1, ratio));
|
||||
}
|
||||
|
||||
ratioTime(ratio) {
|
||||
return ratio * this.props.secondsLoaded + this.props.startOffset;
|
||||
}
|
||||
|
||||
onVideoElementAvailable(videoElement) {
|
||||
this.setState({videoElement});
|
||||
}
|
||||
|
||||
onUserSeek(ratio) {
|
||||
/* ratio in [0,1] */
|
||||
|
||||
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
|
||||
if(ratio === 0) {
|
||||
this.setState({shouldRestartHls: true},
|
||||
funcSeekToRatio);
|
||||
} else {
|
||||
funcSeekToRatio();
|
||||
}
|
||||
}
|
||||
|
||||
onHlsRestart() {
|
||||
this.setState({shouldRestartHls: false})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='cabana-explorer-visuals-camera'>
|
||||
{this.state.isLoading ? this.loadingOverlay() : null}
|
||||
{this.state.shouldShowJpeg ?
|
||||
<img src={this.nearestFrameUrl()}
|
||||
className={css(Styles.img)}
|
||||
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)} />
|
||||
: null }
|
||||
<HLS
|
||||
className={css(Styles.hls)}
|
||||
source={Video.videoUrlForRouteUrl(this.props.url)}
|
||||
startTime={this.props.userSeekTime}
|
||||
playbackSpeed={1}
|
||||
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} />
|
||||
<RouteSeeker
|
||||
className={css(Styles.seekBar)}
|
||||
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}
|
||||
playing={this.props.playing}
|
||||
ratioTime={this.ratioTime} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="cabana-explorer-visuals-camera">
|
||||
{this.state.isLoading ? this.loadingOverlay() : null}
|
||||
{this.state.shouldShowJpeg ? (
|
||||
<img
|
||||
src={this.nearestFrameUrl()}
|
||||
className={css(Styles.img)}
|
||||
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)}
|
||||
/>
|
||||
) : null}
|
||||
<HLS
|
||||
className={css(Styles.hls)}
|
||||
source={Video.videoUrlForRouteUrl(this.props.url)}
|
||||
startTime={this.props.userSeekTime}
|
||||
playbackSpeed={1}
|
||||
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}
|
||||
/>
|
||||
<RouteSeeker
|
||||
className={css(Styles.seekBar)}
|
||||
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}
|
||||
playing={this.props.playing}
|
||||
ratioTime={this.ratioTime}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,227 +1,227 @@
|
|||
import React, {Component} from 'react';
|
||||
import cx from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import FileSaver from 'file-saver';
|
||||
import React, { Component } from "react";
|
||||
import cx from "classnames";
|
||||
import PropTypes from "prop-types";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
import OpenDbc from '../api/OpenDbc';
|
||||
import DBC from '../models/can/dbc';
|
||||
import Modal from './Modals/baseModal';
|
||||
import OpenDbc from "../api/OpenDbc";
|
||||
import DBC from "../models/can/dbc";
|
||||
import Modal from "./Modals/baseModal";
|
||||
// import TabStyles from '../styles/modal-tabs';
|
||||
|
||||
export default class SaveDbcModal extends Component {
|
||||
static propTypes = {
|
||||
dbc: PropTypes.instanceOf(DBC).isRequired,
|
||||
sourceDbcFilename: PropTypes.string.isRequired,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
onDbcSaved: PropTypes.func.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||
hasGithubAuth: PropTypes.bool.isRequired,
|
||||
loginWithGithub: PropTypes.element.isRequired
|
||||
static propTypes = {
|
||||
dbc: PropTypes.instanceOf(DBC).isRequired,
|
||||
sourceDbcFilename: PropTypes.string.isRequired,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
onDbcSaved: PropTypes.func.isRequired,
|
||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||
hasGithubAuth: PropTypes.bool.isRequired,
|
||||
loginWithGithub: PropTypes.element.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tab: "GitHub",
|
||||
openDbcFork: null,
|
||||
dbcFilename: this.props.sourceDbcFilename,
|
||||
tabs: ["GitHub", "Download"]
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tab: 'GitHub',
|
||||
openDbcFork: null,
|
||||
dbcFilename: this.props.sourceDbcFilename,
|
||||
tabs: ['GitHub', 'Download'],
|
||||
};
|
||||
this.commitToGitHub = this.commitToGitHub.bind(this);
|
||||
this.downloadDbcFile = this.downloadDbcFile.bind(this);
|
||||
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
|
||||
this.renderForkButton = this.renderForkButton.bind(this);
|
||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
}
|
||||
|
||||
this.commitToGitHub = this.commitToGitHub.bind(this);
|
||||
this.downloadDbcFile = this.downloadDbcFile.bind(this);
|
||||
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
|
||||
this.renderForkButton = this.renderForkButton.bind(this);
|
||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||
this.renderActions = this.renderActions.bind(this);
|
||||
async componentWillMount() {
|
||||
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
|
||||
this.setState({ openDbcFork });
|
||||
}
|
||||
|
||||
async commitToGitHub() {
|
||||
const { openDbcFork, dbcFilename } = this.state;
|
||||
const filename = dbcFilename.replace(/\.dbc/g, "") + ".dbc";
|
||||
const success = await this.props.openDbcClient.commitFile(
|
||||
openDbcFork,
|
||||
filename,
|
||||
this.props.dbc.text()
|
||||
);
|
||||
if (success) {
|
||||
this.props.onDbcSaved(filename);
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
|
||||
this.setState({openDbcFork})
|
||||
}
|
||||
async downloadDbcFile() {
|
||||
const blob = new Blob([this.props.dbc.text()], {
|
||||
type: "text/plain;charset=utf-8"
|
||||
});
|
||||
const filename = this.state.dbcFilename.replace(/\.dbc/g, "") + ".dbc";
|
||||
FileSaver.saveAs(blob, filename, true);
|
||||
}
|
||||
|
||||
async commitToGitHub() {
|
||||
const { openDbcFork, dbcFilename } = this.state;
|
||||
const filename = dbcFilename.replace(/\.dbc/g, '') + '.dbc';
|
||||
const success = await this.props.openDbcClient.commitFile(openDbcFork,
|
||||
filename,
|
||||
this.props.dbc.text());
|
||||
if (success) {
|
||||
this.props.onDbcSaved(filename);
|
||||
}
|
||||
}
|
||||
|
||||
async downloadDbcFile() {
|
||||
const blob = new Blob([this.props.dbc.text()], {type: "text/plain;charset=utf-8"});
|
||||
const filename = this.state.dbcFilename.replace(/\.dbc/g, '') + '.dbc';
|
||||
FileSaver.saveAs(blob, filename, true);
|
||||
}
|
||||
|
||||
async forkOpenDbcAndWait() {
|
||||
const forkResponseSuccess = await this.props.openDbcClient.fork();
|
||||
if(forkResponseSuccess) {
|
||||
let isTimedOut = false;
|
||||
const timeout = window.setTimeout(() => {
|
||||
isTimedOut = true;
|
||||
}, 30000);
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
if(!isTimedOut) {
|
||||
this.props.openDbcClient.getUserOpenDbcFork().then((openDbcFork) => {
|
||||
if(openDbcFork !== null) {
|
||||
this.setState({openDbcFork});
|
||||
window.clearInterval(interval);
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}, 3000);
|
||||
async forkOpenDbcAndWait() {
|
||||
const forkResponseSuccess = await this.props.openDbcClient.fork();
|
||||
if (forkResponseSuccess) {
|
||||
let isTimedOut = false;
|
||||
const timeout = window.setTimeout(() => {
|
||||
isTimedOut = true;
|
||||
}, 30000);
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
if (!isTimedOut) {
|
||||
this.props.openDbcClient.getUserOpenDbcFork().then(openDbcFork => {
|
||||
if (openDbcFork !== null) {
|
||||
this.setState({ openDbcFork });
|
||||
window.clearInterval(interval);
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// fork failed
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
// fork failed
|
||||
}
|
||||
}
|
||||
|
||||
primaryActionDisabled() {
|
||||
const { tab } = this.state;
|
||||
if (tab === 'GitHub') {
|
||||
return this.state.openDbcFork != null
|
||||
&& this.state.dbcFilename.length > 0
|
||||
} else if (tab === 'Download') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
renderForkButton() {
|
||||
return (
|
||||
<button onClick={ this.forkOpenDbcAndWait }>
|
||||
<i className='fa fa-code-fork'></i>
|
||||
<span> Fork OpenDBC</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
renderForkStep() {
|
||||
const { openDbcFork } = this.state;
|
||||
let content;
|
||||
if (openDbcFork !== null) {
|
||||
content = (
|
||||
<button disabled>
|
||||
<i className='fa fa-code-fork'></i>
|
||||
<span> Forked: { openDbcFork }</span>
|
||||
</button>
|
||||
)
|
||||
} else if (this.props.hasGithubAuth) {
|
||||
content = this.renderForkButton();
|
||||
} else {
|
||||
content = this.props.loginWithGithub;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{ openDbcFork !== null ? this.renderForkButton() : null }
|
||||
{ content }
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderFilenameField() {
|
||||
return (
|
||||
<div className='form-field' data-extension='.dbc'>
|
||||
<label htmlFor='filename'>
|
||||
<span>Choose a filename:</span>
|
||||
<sup>Pick a unique name for your car DBC file</sup>
|
||||
</label>
|
||||
<input type='text'
|
||||
id='filename'
|
||||
value={ this.state.dbcFilename.replace(/\.dbc/g, '') }
|
||||
size={ this.state.dbcFilename.length + 2 }
|
||||
onChange={ (e) =>
|
||||
this.setState({dbcFilename: e.target.value}) } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabNavigation() {
|
||||
primaryActionDisabled() {
|
||||
const { tab } = this.state;
|
||||
if (tab === "GitHub") {
|
||||
return (
|
||||
<div className='cabana-tabs-navigation'>
|
||||
{ this.state.tabs.map((tab) => {
|
||||
return (
|
||||
<a className={ cx({'is-active': this.state.tab === tab})}
|
||||
onClick={ () => { this.setState({ tab }) }}
|
||||
key={tab}>
|
||||
<span>{ tab }</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
this.state.openDbcFork != null && this.state.dbcFilename.length > 0
|
||||
);
|
||||
} else if (tab === "Download") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
renderForkButton() {
|
||||
return (
|
||||
<button onClick={this.forkOpenDbcAndWait}>
|
||||
<i className="fa fa-code-fork" />
|
||||
<span> Fork OpenDBC</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
renderForkStep() {
|
||||
const { openDbcFork } = this.state;
|
||||
let content;
|
||||
if (openDbcFork !== null) {
|
||||
content = (
|
||||
<button disabled>
|
||||
<i className="fa fa-code-fork" />
|
||||
<span> Forked: {openDbcFork}</span>
|
||||
</button>
|
||||
);
|
||||
} else if (this.props.hasGithubAuth) {
|
||||
content = this.renderForkButton();
|
||||
} else {
|
||||
content = this.props.loginWithGithub;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{openDbcFork !== null ? this.renderForkButton() : null}
|
||||
{content}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderFilenameField() {
|
||||
return (
|
||||
<div className="form-field" data-extension=".dbc">
|
||||
<label htmlFor="filename">
|
||||
<span>Choose a filename:</span>
|
||||
<sup>Pick a unique name for your car DBC file</sup>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="filename"
|
||||
value={this.state.dbcFilename.replace(/\.dbc/g, "")}
|
||||
size={this.state.dbcFilename.length + 2}
|
||||
onChange={e => this.setState({ dbcFilename: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabNavigation() {
|
||||
return (
|
||||
<div className="cabana-tabs-navigation">
|
||||
{this.state.tabs.map(tab => {
|
||||
return (
|
||||
<a
|
||||
className={cx({ "is-active": this.state.tab === tab })}
|
||||
onClick={() => {
|
||||
this.setState({ tab });
|
||||
}}
|
||||
key={tab}
|
||||
>
|
||||
<span>{tab}</span>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabContent() {
|
||||
const { tab } = this.state;
|
||||
if (tab === "GitHub") {
|
||||
return (
|
||||
<div>
|
||||
{this.renderForkStep()}
|
||||
{this.renderFilenameField()}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} else if (tab === "Download") {
|
||||
return <div>{this.renderFilenameField()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
renderTabContent() {
|
||||
const { tab } = this.state;
|
||||
if (tab === 'GitHub') {
|
||||
return (
|
||||
<div>
|
||||
{ this.renderForkStep() }
|
||||
{ this.renderFilenameField() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (tab === 'Download') {
|
||||
return (
|
||||
<div>
|
||||
{ this.renderFilenameField() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
renderActions() {
|
||||
const { tab } = this.state;
|
||||
if (tab === "GitHub") {
|
||||
return (
|
||||
<div>
|
||||
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className="button--primary" onClick={this.commitToGitHub}>
|
||||
<span>Commit to GitHub</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else if (tab === "Download") {
|
||||
return (
|
||||
<div>
|
||||
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className="button--primary" onClick={this.downloadDbcFile}>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderActions() {
|
||||
const { tab } = this.state;
|
||||
if (tab === 'GitHub') {
|
||||
return (
|
||||
<div>
|
||||
<button className='button--inverted'
|
||||
onClick={ this.props.handleClose }>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className='button--primary'
|
||||
onClick={ this.commitToGitHub }>
|
||||
<span>Commit to GitHub</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
else if (tab === 'Download') {
|
||||
return (
|
||||
<div>
|
||||
<button className='button--inverted'
|
||||
onClick={ this.props.handleClose }>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button className='button--primary'
|
||||
onClick={ this.downloadDbcFile }>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title='Save DBC File'
|
||||
subtitle='Save your progress and output to a DBC file'
|
||||
handleClose={ this.props.handleClose }
|
||||
navigation={ this.renderTabNavigation() }
|
||||
actions={ this.renderActions() }>
|
||||
{ this.renderTabContent() }
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title="Save DBC File"
|
||||
subtitle="Save your progress and output to a DBC file"
|
||||
handleClose={this.props.handleClose}
|
||||
navigation={this.renderTabNavigation()}
|
||||
actions={this.renderActions()}
|
||||
>
|
||||
{this.renderTabContent()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,365 +1,398 @@
|
|||
// SignalLegendEntry.js
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import cx from "classnames";
|
||||
|
||||
import Signal from '../models/can/signal';
|
||||
import DbcUtils from '../utils/dbc';
|
||||
import {swapKeysAndValues} from '../utils/object';
|
||||
import Signal from "../models/can/signal";
|
||||
import DbcUtils from "../utils/dbc";
|
||||
import { swapKeysAndValues } from "../utils/object";
|
||||
|
||||
export default class SignalLegendEntry extends Component {
|
||||
static propTypes = {
|
||||
signal: PropTypes.instanceOf(Signal).isRequired,
|
||||
isHighlighted: PropTypes.bool,
|
||||
onSignalHover: PropTypes.func,
|
||||
onSignalHoverEnd: PropTypes.func,
|
||||
onTentativeSignalChange: PropTypes.func,
|
||||
onSignalChange: PropTypes.func,
|
||||
onSignalRemove: PropTypes.func,
|
||||
onSignalPlotChange: PropTypes.func,
|
||||
toggleExpandSignal: PropTypes.func,
|
||||
isPlotted: PropTypes.bool,
|
||||
isExpanded: PropTypes.bool,
|
||||
static propTypes = {
|
||||
signal: PropTypes.instanceOf(Signal).isRequired,
|
||||
isHighlighted: PropTypes.bool,
|
||||
onSignalHover: PropTypes.func,
|
||||
onSignalHoverEnd: PropTypes.func,
|
||||
onTentativeSignalChange: PropTypes.func,
|
||||
onSignalChange: PropTypes.func,
|
||||
onSignalRemove: PropTypes.func,
|
||||
onSignalPlotChange: PropTypes.func,
|
||||
toggleExpandSignal: PropTypes.func,
|
||||
isPlotted: PropTypes.bool,
|
||||
isExpanded: PropTypes.bool
|
||||
};
|
||||
|
||||
static unsignedTransformation = field => {
|
||||
return (value, signal) => {
|
||||
if (value !== "") {
|
||||
value = Number(value) || 0;
|
||||
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
signal[field] = value;
|
||||
return signal;
|
||||
};
|
||||
};
|
||||
|
||||
static unsignedTransformation = (field) => {
|
||||
return (value, signal) => {
|
||||
if(value !== '') {
|
||||
value = Number(value) || 0;
|
||||
static fields = [
|
||||
{
|
||||
field: "name",
|
||||
title: "Name",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
field: "size",
|
||||
title: "Size",
|
||||
type: "number",
|
||||
transform: SignalLegendEntry.unsignedTransformation("size")
|
||||
},
|
||||
{
|
||||
field: "startBit",
|
||||
title: signal =>
|
||||
signal.isLittleEndian
|
||||
? "Least significant bit"
|
||||
: "Most significant bit",
|
||||
type: "number",
|
||||
transform: SignalLegendEntry.unsignedTransformation("startBit")
|
||||
},
|
||||
{
|
||||
field: "isLittleEndian",
|
||||
title: "Endianness",
|
||||
type: "option",
|
||||
options: {
|
||||
options: ["Little", "Big"],
|
||||
optionValues: { Little: true, Big: false }
|
||||
},
|
||||
transform: (isLittleEndian, signal) => {
|
||||
if (signal.isLittleEndian !== isLittleEndian) {
|
||||
const { startBit } = signal;
|
||||
|
||||
if(value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
if (isLittleEndian) {
|
||||
// big endian -> little endian
|
||||
const startByte = Math.floor(signal.startBit / 8),
|
||||
endByte = Math.floor((signal.startBit - signal.size + 1) / 8);
|
||||
|
||||
if (startByte === endByte) {
|
||||
signal.startBit = signal.startBit - signal.size + 1;
|
||||
} else {
|
||||
signal.startBit = DbcUtils.matrixBitNumber(startBit);
|
||||
}
|
||||
signal[field] = value;
|
||||
return signal;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
// little endian -> big endian
|
||||
const startByte = Math.floor(signal.startBit / 8),
|
||||
endByte = Math.floor((signal.startBit + signal.size - 1) / 8);
|
||||
|
||||
static fields = [
|
||||
{
|
||||
field: 'name',
|
||||
title: 'Name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
field: 'size',
|
||||
title: 'Size',
|
||||
type: 'number',
|
||||
transform: SignalLegendEntry.unsignedTransformation('size')
|
||||
},
|
||||
{
|
||||
field: 'startBit',
|
||||
title: (signal) => signal.isLittleEndian ? 'Least significant bit' : 'Most significant bit',
|
||||
type: 'number',
|
||||
transform: SignalLegendEntry.unsignedTransformation('startBit')
|
||||
},
|
||||
{
|
||||
field: 'isLittleEndian',
|
||||
title: 'Endianness',
|
||||
type: 'option',
|
||||
options: {
|
||||
options: ['Little', 'Big'],
|
||||
optionValues: {Little: true, Big: false}
|
||||
},
|
||||
transform: (isLittleEndian, signal) => {
|
||||
if(signal.isLittleEndian !== isLittleEndian) {
|
||||
const {startBit} = signal;
|
||||
|
||||
if(isLittleEndian) {
|
||||
// big endian -> little endian
|
||||
const startByte = Math.floor(signal.startBit / 8),
|
||||
endByte = Math.floor((signal.startBit - signal.size + 1) / 8);
|
||||
|
||||
if(startByte === endByte) {
|
||||
signal.startBit = signal.startBit - signal.size + 1;
|
||||
} else {
|
||||
signal.startBit = DbcUtils.matrixBitNumber(startBit);
|
||||
}
|
||||
} else {
|
||||
// little endian -> big endian
|
||||
const startByte = Math.floor(signal.startBit / 8),
|
||||
endByte = Math.floor((signal.startBit + signal.size - 1) / 8);
|
||||
|
||||
if(startByte === endByte) {
|
||||
signal.startBit = signal.startBit + signal.size - 1;
|
||||
} else {
|
||||
signal.startBit = DbcUtils.bigEndianBitIndex(startBit);
|
||||
}
|
||||
}
|
||||
signal.isLittleEndian = isLittleEndian;
|
||||
}
|
||||
return signal;
|
||||
if (startByte === endByte) {
|
||||
signal.startBit = signal.startBit + signal.size - 1;
|
||||
} else {
|
||||
signal.startBit = DbcUtils.bigEndianBitIndex(startBit);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'isSigned',
|
||||
title: 'Sign',
|
||||
type: 'option',
|
||||
options: {options: ['Signed', 'Unsigned'],
|
||||
optionValues: {Signed: true, Unsigned: false}}
|
||||
},
|
||||
{
|
||||
field: 'factor',
|
||||
title: 'Factor',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
field: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
field: 'unit',
|
||||
title: 'Unit',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
field: 'comment',
|
||||
title: 'Comment',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
field: 'min',
|
||||
title: 'Minimum value',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
field: 'max',
|
||||
title: 'Maximum value',
|
||||
type: 'number'
|
||||
}
|
||||
signal.isLittleEndian = isLittleEndian;
|
||||
}
|
||||
];
|
||||
return signal;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: "isSigned",
|
||||
title: "Sign",
|
||||
type: "option",
|
||||
options: {
|
||||
options: ["Signed", "Unsigned"],
|
||||
optionValues: { Signed: true, Unsigned: false }
|
||||
}
|
||||
},
|
||||
{
|
||||
field: "factor",
|
||||
title: "Factor",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
field: "offset",
|
||||
title: "Offset",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
field: "unit",
|
||||
title: "Unit",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
field: "comment",
|
||||
title: "Comment",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
field: "min",
|
||||
title: "Minimum value",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
field: "max",
|
||||
title: "Maximum value",
|
||||
type: "number"
|
||||
}
|
||||
];
|
||||
|
||||
static fieldSpecForName = (name) => {
|
||||
return SignalLegendEntry.fields.find((field) =>
|
||||
field.field === name)
|
||||
static fieldSpecForName = name => {
|
||||
return SignalLegendEntry.fields.find(field => field.field === name);
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isExpanded: false,
|
||||
signalEdited: Object.assign(Object.create(props.signal), props.signal)
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isExpanded: false,
|
||||
signalEdited: Object.assign(Object.create(props.signal), props.signal),
|
||||
};
|
||||
this.toggleEditing = this.toggleEditing.bind(this);
|
||||
this.updateField = this.updateField.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
}
|
||||
|
||||
this.toggleEditing = this.toggleEditing.bind(this);
|
||||
this.updateField = this.updateField.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(!nextProps.signal.equals(this.props.signal)) {
|
||||
this.setState({signalEdited: Object.assign(Object.create(nextProps.signal),
|
||||
nextProps.signal)});
|
||||
}
|
||||
}
|
||||
|
||||
renderField(field, title, valueCol, signal) {
|
||||
let titleStr;
|
||||
if(typeof title === 'function') {
|
||||
titleStr = title(signal);
|
||||
} else {
|
||||
titleStr = title;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field} className='form-field form-field--small'>
|
||||
<label htmlFor={`${signal.name}_${field}`}>{titleStr}</label>
|
||||
{valueCol}
|
||||
</div>
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.signal.equals(this.props.signal)) {
|
||||
this.setState({
|
||||
signalEdited: Object.assign(
|
||||
Object.create(nextProps.signal),
|
||||
nextProps.signal
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderField(field, title, valueCol, signal) {
|
||||
let titleStr;
|
||||
if (typeof title === "function") {
|
||||
titleStr = title(signal);
|
||||
} else {
|
||||
titleStr = title;
|
||||
}
|
||||
|
||||
updateField(fieldSpec, value) {
|
||||
let {signalEdited} = this.state;
|
||||
const {signal} = this.props;
|
||||
return (
|
||||
<div key={field} className="form-field form-field--small">
|
||||
<label htmlFor={`${signal.name}_${field}`}>{titleStr}</label>
|
||||
{valueCol}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(fieldSpec.transform) {
|
||||
signalEdited = fieldSpec.transform(value, signalEdited);
|
||||
} else {
|
||||
signalEdited[fieldSpec.field] = value;
|
||||
}
|
||||
updateField(fieldSpec, value) {
|
||||
let { signalEdited } = this.state;
|
||||
const { signal } = this.props;
|
||||
|
||||
|
||||
// Save entire signal while editing
|
||||
this.setState({signalEdited});
|
||||
const signalCopy = Object.assign(Object.create(signal), signal);
|
||||
Object.entries(signalEdited).forEach(([field, value]) => {
|
||||
signalCopy[field] = value;
|
||||
});
|
||||
this.props.onSignalChange(signalCopy, signal);
|
||||
if (fieldSpec.transform) {
|
||||
signalEdited = fieldSpec.transform(value, signalEdited);
|
||||
} else {
|
||||
signalEdited[fieldSpec.field] = value;
|
||||
}
|
||||
|
||||
renderNumberField(fieldSpec, signal) {
|
||||
const {field, title} = fieldSpec;
|
||||
let valueCol;
|
||||
// Save entire signal while editing
|
||||
this.setState({ signalEdited });
|
||||
const signalCopy = Object.assign(Object.create(signal), signal);
|
||||
Object.entries(signalEdited).forEach(([field, value]) => {
|
||||
signalCopy[field] = value;
|
||||
});
|
||||
this.props.onSignalChange(signalCopy, signal);
|
||||
}
|
||||
|
||||
if(this.props.isExpanded) {
|
||||
let value = this.state.signalEdited[field];
|
||||
if(value !== '') {
|
||||
let num = Number(value);
|
||||
value = (isNaN(num) ? '' : num);
|
||||
}
|
||||
valueCol = (
|
||||
<input id={`${signal.name}_${field}`}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={(e) => {this.updateField(fieldSpec, e.target.value)}
|
||||
}/>
|
||||
);
|
||||
} else {
|
||||
let value = this.props.signal[field];
|
||||
valueCol = <span>{value}</span>;
|
||||
}
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
renderNumberField(fieldSpec, signal) {
|
||||
const { field, title } = fieldSpec;
|
||||
let valueCol;
|
||||
|
||||
renderStringField(fieldSpec, signal) {
|
||||
const {field, title} = fieldSpec;
|
||||
let valueCol;
|
||||
if(this.props.isExpanded) {
|
||||
valueCol = (
|
||||
<input id={`${signal.name}_${field}`}
|
||||
type="text"
|
||||
value={this.state.signalEdited[field] || ''}
|
||||
onChange={(e) => {
|
||||
this.updateField(fieldSpec, e.target.value)
|
||||
}}
|
||||
/>);
|
||||
} else {
|
||||
valueCol = <span>{this.props.signal[field]}</span>;
|
||||
}
|
||||
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
renderOptionField(fieldSpec, signal) {
|
||||
let valueCol;
|
||||
const {field, title} = fieldSpec;
|
||||
const {options, optionValues} = fieldSpec.options;
|
||||
let valueOptions = swapKeysAndValues(optionValues);
|
||||
|
||||
if(this.props.isExpanded) {
|
||||
const optionEles = options.map((opt) =>
|
||||
<option key={opt}
|
||||
value={optionValues[opt]}>{opt}</option>
|
||||
);
|
||||
valueCol = (
|
||||
<select id={`${signal.name}_${field}`}
|
||||
defaultValue={this.state.signalEdited[field]}
|
||||
onChange={
|
||||
(e) => { this.updateField(fieldSpec, e.target.value === "true") }
|
||||
}>
|
||||
{optionEles}
|
||||
</select>
|
||||
);
|
||||
} else {
|
||||
valueCol = <span>{valueOptions[this.props.signal[field]]}</span>;
|
||||
}
|
||||
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
renderFieldNode(field, signal) {
|
||||
if(field.type === 'number') {
|
||||
return this.renderNumberField(field, signal);
|
||||
} else if(field.type === 'option') {
|
||||
return this.renderOptionField(field, signal);
|
||||
} else if(field.type === 'string') {
|
||||
return this.renderStringField(field, signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toggleEditing(e) {
|
||||
let { signalEdited } = this.state;
|
||||
const { signal, isExpanded } = this.props;
|
||||
const signalCopy = Object.assign(Object.create(signal), signal);
|
||||
|
||||
if(isExpanded) {
|
||||
// Finished editing, save changes & reset intermediate
|
||||
// signalEdited state.
|
||||
Object.entries(signalEdited).forEach(([field, value]) => {
|
||||
const fieldSpec = SignalLegendEntry.fieldSpecForName(field);
|
||||
|
||||
if(fieldSpec && fieldSpec.type === 'number' && isNaN(parseInt(value, 10))) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
signalCopy[field] = value;
|
||||
});
|
||||
this.props.onSignalChange(signalCopy, signal);
|
||||
} else {
|
||||
signalEdited = signalCopy;
|
||||
}
|
||||
|
||||
// Expand and enable signal editing
|
||||
this.setState({
|
||||
signalEdited
|
||||
})
|
||||
this.props.toggleExpandSignal(signal);
|
||||
e.stopPropagation();
|
||||
|
||||
}
|
||||
|
||||
renderSignalForm(signal) {
|
||||
return (
|
||||
<div className='signals-legend-entry-form'>
|
||||
{SignalLegendEntry.fields.map((field) => {
|
||||
return (
|
||||
<div className='signals-legend-entry-form-field'
|
||||
key={field.field}>
|
||||
{this.renderFieldNode(field, signal)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className='signals-legend-entry-form-remove'>
|
||||
<button className='button--tiny button--alpha'
|
||||
onClick={ () => this.props.onSignalRemove(signal) }>Remove Signal</button>
|
||||
</div>
|
||||
</div>
|
||||
if (this.props.isExpanded) {
|
||||
let value = this.state.signalEdited[field];
|
||||
if (value !== "") {
|
||||
let num = Number(value);
|
||||
value = isNaN(num) ? "" : num;
|
||||
}
|
||||
valueCol = (
|
||||
<input
|
||||
id={`${signal.name}_${field}`}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={e => {
|
||||
this.updateField(fieldSpec, e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
let value = this.props.signal[field];
|
||||
valueCol = <span>{value}</span>;
|
||||
}
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
renderStringField(fieldSpec, signal) {
|
||||
const { field, title } = fieldSpec;
|
||||
let valueCol;
|
||||
if (this.props.isExpanded) {
|
||||
valueCol = (
|
||||
<input
|
||||
id={`${signal.name}_${field}`}
|
||||
type="text"
|
||||
value={this.state.signalEdited[field] || ""}
|
||||
onChange={e => {
|
||||
this.updateField(fieldSpec, e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
valueCol = <span>{this.props.signal[field]}</span>;
|
||||
}
|
||||
|
||||
toggleSignalPlot(e) {
|
||||
const {signal, isPlotted} = this.props;
|
||||
e.preventDefault();
|
||||
this.props.onSignalPlotChange(!isPlotted, signal.uid);
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
renderOptionField(fieldSpec, signal) {
|
||||
let valueCol;
|
||||
const { field, title } = fieldSpec;
|
||||
const { options, optionValues } = fieldSpec.options;
|
||||
let valueOptions = swapKeysAndValues(optionValues);
|
||||
|
||||
if (this.props.isExpanded) {
|
||||
const optionEles = options.map(opt => (
|
||||
<option key={opt} value={optionValues[opt]}>
|
||||
{opt}
|
||||
</option>
|
||||
));
|
||||
valueCol = (
|
||||
<select
|
||||
id={`${signal.name}_${field}`}
|
||||
defaultValue={this.state.signalEdited[field]}
|
||||
onChange={e => {
|
||||
this.updateField(fieldSpec, e.target.value === "true");
|
||||
}}
|
||||
>
|
||||
{optionEles}
|
||||
</select>
|
||||
);
|
||||
} else {
|
||||
valueCol = <span>{valueOptions[this.props.signal[field]]}</span>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {signal} = this.props;
|
||||
const expandedEntryClass = this.props.isExpanded ? 'is-expanded' : null;
|
||||
const highlightedEntryClass = this.props.isHighlighted ? 'is-highlighted' : null;
|
||||
const plottedButtonClass = this.props.isPlotted ? 'button' : 'button--alpha';
|
||||
const plottedButtonText = this.props.isPlotted ? 'Hide Plot' : 'Show Plot';
|
||||
return (
|
||||
return this.renderField(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
renderFieldNode(field, signal) {
|
||||
if (field.type === "number") {
|
||||
return this.renderNumberField(field, signal);
|
||||
} else if (field.type === "option") {
|
||||
return this.renderOptionField(field, signal);
|
||||
} else if (field.type === "string") {
|
||||
return this.renderStringField(field, signal);
|
||||
}
|
||||
}
|
||||
|
||||
toggleEditing(e) {
|
||||
let { signalEdited } = this.state;
|
||||
const { signal, isExpanded } = this.props;
|
||||
const signalCopy = Object.assign(Object.create(signal), signal);
|
||||
|
||||
if (isExpanded) {
|
||||
// Finished editing, save changes & reset intermediate
|
||||
// signalEdited state.
|
||||
Object.entries(signalEdited).forEach(([field, value]) => {
|
||||
const fieldSpec = SignalLegendEntry.fieldSpecForName(field);
|
||||
|
||||
if (
|
||||
fieldSpec &&
|
||||
fieldSpec.type === "number" &&
|
||||
isNaN(parseInt(value, 10))
|
||||
) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
signalCopy[field] = value;
|
||||
});
|
||||
this.props.onSignalChange(signalCopy, signal);
|
||||
} else {
|
||||
signalEdited = signalCopy;
|
||||
}
|
||||
|
||||
// Expand and enable signal editing
|
||||
this.setState({
|
||||
signalEdited
|
||||
});
|
||||
this.props.toggleExpandSignal(signal);
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
renderSignalForm(signal) {
|
||||
return (
|
||||
<div className="signals-legend-entry-form">
|
||||
{SignalLegendEntry.fields.map(field => {
|
||||
return (
|
||||
<div className="signals-legend-entry-form-field" key={field.field}>
|
||||
{this.renderFieldNode(field, signal)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="signals-legend-entry-form-remove">
|
||||
<button
|
||||
className="button--tiny button--alpha"
|
||||
onClick={() => this.props.onSignalRemove(signal)}
|
||||
>
|
||||
Remove Signal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
toggleSignalPlot(e) {
|
||||
const { signal, isPlotted } = this.props;
|
||||
e.preventDefault();
|
||||
this.props.onSignalPlotChange(!isPlotted, signal.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { signal } = this.props;
|
||||
const expandedEntryClass = this.props.isExpanded ? "is-expanded" : null;
|
||||
const highlightedEntryClass = this.props.isHighlighted
|
||||
? "is-highlighted"
|
||||
: null;
|
||||
const plottedButtonClass = this.props.isPlotted
|
||||
? "button"
|
||||
: "button--alpha";
|
||||
const plottedButtonText = this.props.isPlotted ? "Hide Plot" : "Show Plot";
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"signals-legend-entry",
|
||||
expandedEntryClass,
|
||||
highlightedEntryClass
|
||||
)}
|
||||
onMouseEnter={() => this.props.onSignalHover(signal)}
|
||||
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}
|
||||
>
|
||||
<div
|
||||
className="signals-legend-entry-color"
|
||||
style={{ backgroundColor: `rgb(${this.props.color}` }}
|
||||
/>
|
||||
<div className="signals-legend-entry-header">
|
||||
<div
|
||||
className={cx('signals-legend-entry', expandedEntryClass, highlightedEntryClass)}
|
||||
onMouseEnter={() => this.props.onSignalHover(signal)}
|
||||
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}>
|
||||
<div className='signals-legend-entry-color'
|
||||
style={{backgroundColor: `rgb(${this.props.color}`}}></div>
|
||||
<div className="signals-legend-entry-header">
|
||||
<div
|
||||
className="signals-legend-entry-header-name"
|
||||
onClick={this.toggleEditing}>
|
||||
<strong>{signal.name}</strong>
|
||||
</div>
|
||||
<div
|
||||
className="signals-legend-entry-header-action"
|
||||
onClick={this.toggleSignalPlot}>
|
||||
<button className={cx('button--tiny', plottedButtonClass)}>
|
||||
{plottedButtonText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="signals-legend-entry-body">
|
||||
{this.props.isExpanded ? this.renderSignalForm(signal) : null}
|
||||
</div>
|
||||
className="signals-legend-entry-header-name"
|
||||
onClick={this.toggleEditing}
|
||||
>
|
||||
<strong>{signal.name}</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div
|
||||
className="signals-legend-entry-header-action"
|
||||
onClick={this.toggleSignalPlot}
|
||||
>
|
||||
<button className={cx("button--tiny", plottedButtonClass)}>
|
||||
{plottedButtonText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="signals-legend-entry-body">
|
||||
{this.props.isExpanded ? this.renderSignalForm(signal) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import {getUrlParameter} from './utils/url';
|
||||
import { getUrlParameter } from "./utils/url";
|
||||
|
||||
const ENV = process.env.NODE_ENV === "production" ? "prod" : "debug";
|
||||
|
||||
const ENV = process.env.NODE_ENV === 'production' ? 'prod' : 'debug';
|
||||
|
||||
const ENV_GITHUB_CLIENT_ID = {debug: 'f1e42d14f45491f9ca34',
|
||||
prod: '4b43250e7499a97d62a5'}
|
||||
const ENV_GITHUB_CLIENT_ID = {
|
||||
debug: "f1e42d14f45491f9ca34",
|
||||
prod: "4b43250e7499a97d62a5"
|
||||
};
|
||||
export const GITHUB_CLIENT_ID = ENV_GITHUB_CLIENT_ID[ENV];
|
||||
|
||||
const ENV_GITHUB_REDIRECT_URL = {debug: 'http://127.0.0.1:1235/callback',
|
||||
prod: 'https://api.comma.ai/cabana/ghcallback'}
|
||||
const ENV_GITHUB_REDIRECT_URL = {
|
||||
debug: "http://127.0.0.1:1235/callback",
|
||||
prod: "https://api.comma.ai/cabana/ghcallback"
|
||||
};
|
||||
export const GITHUB_REDIRECT_URL = ENV_GITHUB_REDIRECT_URL[ENV];
|
||||
export const GITHUB_AUTH_TOKEN_KEY = 'gh_access_token';
|
||||
export const OPENDBC_SOURCE_REPO = 'commaai/opendbc';
|
||||
export const GITHUB_AUTH_TOKEN_KEY = "gh_access_token";
|
||||
export const OPENDBC_SOURCE_REPO = "commaai/opendbc";
|
||||
|
||||
export const USE_UNLOGGER = (typeof window !== 'undefined' && getUrlParameter('unlogger') !== null);
|
||||
export const UNLOGGER_HOST = 'http://localhost:8080/unlogger';
|
||||
export const USE_UNLOGGER =
|
||||
typeof window !== "undefined" && getUrlParameter("unlogger") !== null;
|
||||
export const UNLOGGER_HOST = "http://localhost:8080/unlogger";
|
||||
|
||||
export const LOGENTRIES_TOKEN = '4bc98019-8277-4fe0-867c-ed21ea843cc5';
|
||||
export const LOGENTRIES_TOKEN = "4bc98019-8277-4fe0-867c-ed21ea843cc5";
|
||||
|
||||
export const PART_SEGMENT_LENGTH = 3;
|
||||
|
||||
|
@ -24,5 +28,5 @@ export const CAN_GRAPH_MAX_POINTS = 10000;
|
|||
|
||||
export const STREAMING_WINDOW = 60;
|
||||
|
||||
export const COMMA_ACCESS_TOKEN_COOKIE = 'comma_access_token';
|
||||
export const COMMA_OAUTH_REDIRECT_COOKIE = 'wiki_login_redirect';
|
||||
export const COMMA_ACCESS_TOKEN_COOKIE = "comma_access_token";
|
||||
export const COMMA_OAUTH_REDIRECT_COOKIE = "wiki_login_redirect";
|
||||
|
|
|
@ -1,49 +1,53 @@
|
|||
import LogEntries from './LogEntries';
|
||||
import {LOGENTRIES_TOKEN} from '../config';
|
||||
import LogEntries from "./LogEntries";
|
||||
import { LOGENTRIES_TOKEN } from "../config";
|
||||
|
||||
class CloudLog {
|
||||
constructor() {
|
||||
LogEntries.init({token: LOGENTRIES_TOKEN,
|
||||
no_format: true,
|
||||
catchall: false});
|
||||
this.context = {};
|
||||
constructor() {
|
||||
LogEntries.init({
|
||||
token: LOGENTRIES_TOKEN,
|
||||
no_format: true,
|
||||
catchall: false
|
||||
});
|
||||
this.context = {};
|
||||
}
|
||||
|
||||
bind(obj) {
|
||||
this.context.update(obj);
|
||||
}
|
||||
|
||||
emit(message, level = "log") {
|
||||
if (typeof global.__JEST__ !== "undefined") {
|
||||
// Don't log in testing environment
|
||||
return;
|
||||
}
|
||||
|
||||
bind(obj) {
|
||||
this.context.update(obj);
|
||||
const entry = {
|
||||
ctx: this.context,
|
||||
created: new Date().getTime() / 1000,
|
||||
msg: message,
|
||||
src: "JSCloudLog"
|
||||
};
|
||||
|
||||
if (level === "log") {
|
||||
LogEntries.log(entry);
|
||||
} else if (level === "warn") {
|
||||
LogEntries.warn(entry);
|
||||
} else if (level === "error") {
|
||||
LogEntries.error(entry);
|
||||
}
|
||||
}
|
||||
|
||||
emit(message, level = 'log') {
|
||||
if(typeof global.__JEST__ !== 'undefined') {
|
||||
// Don't log in testing environment
|
||||
return
|
||||
}
|
||||
log(message) {
|
||||
this.emit(message);
|
||||
}
|
||||
|
||||
const entry = {ctx: this.context,
|
||||
created: new Date().getTime() / 1000,
|
||||
msg: message,
|
||||
src: 'JSCloudLog'};
|
||||
warn(message) {
|
||||
this.emit(message, "warn");
|
||||
}
|
||||
|
||||
if(level === 'log') {
|
||||
LogEntries.log(entry);
|
||||
} else if(level === 'warn') {
|
||||
LogEntries.warn(entry);
|
||||
} else if(level === 'error') {
|
||||
LogEntries.error(entry);
|
||||
}
|
||||
}
|
||||
|
||||
log(message) {
|
||||
this.emit(message);
|
||||
}
|
||||
|
||||
warn(message) {
|
||||
this.emit(message, 'warn');
|
||||
}
|
||||
|
||||
error(message) {
|
||||
this.emit(message, 'error');
|
||||
}
|
||||
error(message) {
|
||||
this.emit(message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
export default (new CloudLog());
|
||||
export default new CloudLog();
|
||||
|
|
|
@ -1,382 +1,388 @@
|
|||
// Vendored from https://github.com/rapid7/le_js, which is broken with webpack.
|
||||
/* eslint-disable */
|
||||
|
||||
if (typeof window === 'undefined') { // eslint-disable-line no-use-before-define
|
||||
if (typeof window === "undefined") {
|
||||
// eslint-disable-line no-use-before-define
|
||||
var window = self;
|
||||
}
|
||||
var _indexOf = function (array, obj) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (obj === array[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
var _indexOf = function(array, obj) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (obj === array[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Obtain a browser-specific XHR object
|
||||
var _getAjaxObject = function () {
|
||||
if (typeof XDomainRequest !== "undefined") {
|
||||
// We're using IE8/9
|
||||
return new XDomainRequest();
|
||||
}
|
||||
return new XMLHttpRequest();
|
||||
var _getAjaxObject = function() {
|
||||
if (typeof XDomainRequest !== "undefined") {
|
||||
// We're using IE8/9
|
||||
return new XDomainRequest();
|
||||
}
|
||||
return new XMLHttpRequest();
|
||||
};
|
||||
|
||||
/**
|
||||
* A single log event stream.
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
* A single log event stream.
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
function LogStream(options) {
|
||||
/**
|
||||
* @const
|
||||
* @type {string} */
|
||||
var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null;
|
||||
/** @type {string} */
|
||||
var _pageInfo = options.page_info;
|
||||
/** @type {string} */
|
||||
var _token = options.token;
|
||||
/** @type {boolean} */
|
||||
var _print = options.print;
|
||||
/** @type {boolean} */
|
||||
var _noFormat = options.no_format;
|
||||
/** @type {boolean} */
|
||||
var _SSL = function() {
|
||||
if (typeof XDomainRequest === "undefined") {
|
||||
return options.ssl;
|
||||
}
|
||||
// If we're relying on XDomainRequest, we
|
||||
// must adhere to the page's encryption scheme.
|
||||
return window.location.protocol === "https:" ? true : false;
|
||||
}();
|
||||
/** @type {string} */
|
||||
var _endpoint;
|
||||
if (window.LEENDPOINT) {
|
||||
_endpoint = window.LEENDPOINT;
|
||||
} else if (_noFormat) {
|
||||
_endpoint = "webhook.logentries.com/noformat";
|
||||
}
|
||||
else {
|
||||
_endpoint = "js.logentries.com/v1";
|
||||
}
|
||||
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
|
||||
/**
|
||||
* @const
|
||||
* @type {string} */
|
||||
var _traceCode = options.trace
|
||||
? (Math.random() + Math.PI).toString(36).substring(2, 10)
|
||||
: null;
|
||||
/** @type {string} */
|
||||
var _pageInfo = options.page_info;
|
||||
/** @type {string} */
|
||||
var _token = options.token;
|
||||
/** @type {boolean} */
|
||||
var _print = options.print;
|
||||
/** @type {boolean} */
|
||||
var _noFormat = options.no_format;
|
||||
/** @type {boolean} */
|
||||
var _SSL = (function() {
|
||||
if (typeof XDomainRequest === "undefined") {
|
||||
return options.ssl;
|
||||
}
|
||||
// If we're relying on XDomainRequest, we
|
||||
// must adhere to the page's encryption scheme.
|
||||
return window.location.protocol === "https:" ? true : false;
|
||||
})();
|
||||
/** @type {string} */
|
||||
var _endpoint;
|
||||
if (window.LEENDPOINT) {
|
||||
_endpoint = window.LEENDPOINT;
|
||||
} else if (_noFormat) {
|
||||
_endpoint = "webhook.logentries.com/noformat";
|
||||
} else {
|
||||
_endpoint = "js.logentries.com/v1";
|
||||
}
|
||||
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
|
||||
|
||||
/**
|
||||
* Flag to prevent further invocations on network err
|
||||
** @type {boolean} */
|
||||
var _shouldCall = true;
|
||||
/** @type {Array.<string>} */
|
||||
var _backlog = [];
|
||||
/** @type {boolean} */
|
||||
var _active = false;
|
||||
/** @type {boolean} */
|
||||
var _sentPageInfo = false;
|
||||
/**
|
||||
* Flag to prevent further invocations on network err
|
||||
** @type {boolean} */
|
||||
var _shouldCall = true;
|
||||
/** @type {Array.<string>} */
|
||||
var _backlog = [];
|
||||
/** @type {boolean} */
|
||||
var _active = false;
|
||||
/** @type {boolean} */
|
||||
var _sentPageInfo = false;
|
||||
|
||||
var _apiCall = function(token, data) {
|
||||
_active = true;
|
||||
var _apiCall = function(token, data) {
|
||||
_active = true;
|
||||
|
||||
var request = _getAjaxObject();
|
||||
var request = _getAjaxObject();
|
||||
|
||||
if (_shouldCall) {
|
||||
if (request.constructor === XMLHttpRequest) {
|
||||
// Currently we don't support fine-grained error
|
||||
// handling in older versions of IE
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === 4) {
|
||||
// Handle any errors
|
||||
if (request.status >= 400) {
|
||||
console.error("Couldn't submit events.");
|
||||
if (request.status === 410) {
|
||||
// This API version has been phased out
|
||||
console.warn("This version of le_js is no longer supported!");
|
||||
}
|
||||
} else {
|
||||
if (request.status === 301) {
|
||||
// Server issued a deprecation warning
|
||||
console.warn("This version of le_js is deprecated! Consider upgrading.");
|
||||
}
|
||||
if (_backlog.length > 0) {
|
||||
// Submit the next event in the backlog
|
||||
_apiCall(token, _backlog.shift());
|
||||
} else {
|
||||
_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_shouldCall) {
|
||||
if (request.constructor === XMLHttpRequest) {
|
||||
// Currently we don't support fine-grained error
|
||||
// handling in older versions of IE
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === 4) {
|
||||
// Handle any errors
|
||||
if (request.status >= 400) {
|
||||
console.error("Couldn't submit events.");
|
||||
if (request.status === 410) {
|
||||
// This API version has been phased out
|
||||
console.warn("This version of le_js is no longer supported!");
|
||||
}
|
||||
} else {
|
||||
if (request.status === 301) {
|
||||
// Server issued a deprecation warning
|
||||
console.warn(
|
||||
"This version of le_js is deprecated! Consider upgrading."
|
||||
);
|
||||
}
|
||||
if (_backlog.length > 0) {
|
||||
// Submit the next event in the backlog
|
||||
_apiCall(token, _backlog.shift());
|
||||
} else {
|
||||
_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
request.onload = function() {
|
||||
if (_backlog.length > 0) {
|
||||
// Submit the next event in the backlog
|
||||
_apiCall(token, _backlog.shift());
|
||||
} else {
|
||||
_active = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
} else {
|
||||
request.onload = function() {
|
||||
if (_backlog.length > 0) {
|
||||
// Submit the next event in the backlog
|
||||
_apiCall(token, _backlog.shift());
|
||||
} else {
|
||||
_active = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
request.open("POST", _endpoint, true);
|
||||
if (request.constructor === XMLHttpRequest) {
|
||||
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
}
|
||||
|
||||
request.open("POST", _endpoint, true);
|
||||
if (request.constructor === XMLHttpRequest) {
|
||||
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
request.setRequestHeader('Content-type', 'application/json');
|
||||
}
|
||||
if (request.overrideMimeType) {
|
||||
request.overrideMimeType("text");
|
||||
}
|
||||
|
||||
if (request.overrideMimeType) {
|
||||
request.overrideMimeType('text');
|
||||
}
|
||||
request.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
request.send(data);
|
||||
}
|
||||
};
|
||||
var _getEvent = function() {
|
||||
var raw = null;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
if (args.length === 0) {
|
||||
throw new Error("No arguments!");
|
||||
} else if (args.length === 1) {
|
||||
raw = args[0];
|
||||
} else {
|
||||
// Handle a variadic overload,
|
||||
// e.g. _rawLog("some text ", x, " ...", 1);
|
||||
raw = args;
|
||||
}
|
||||
return raw;
|
||||
};
|
||||
|
||||
var _getEvent = function() {
|
||||
var raw = null;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
if (args.length === 0) {
|
||||
throw new Error("No arguments!");
|
||||
} else if (args.length === 1) {
|
||||
raw = args[0];
|
||||
} else {
|
||||
// Handle a variadic overload,
|
||||
// e.g. _rawLog("some text ", x, " ...", 1);
|
||||
raw = args;
|
||||
}
|
||||
return raw;
|
||||
};
|
||||
var _agentInfo = function() {
|
||||
var nav = window.navigator || { doNotTrack: undefined };
|
||||
var screen = window.screen || {};
|
||||
var location = window.location || {};
|
||||
|
||||
var _agentInfo = function() {
|
||||
var nav = window.navigator || {doNotTrack: undefined};
|
||||
var screen = window.screen || {};
|
||||
var location = window.location || {};
|
||||
return {
|
||||
url: location.pathname,
|
||||
referrer: document.referrer,
|
||||
screen: {
|
||||
width: screen.width,
|
||||
height: screen.height
|
||||
},
|
||||
window: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
},
|
||||
browser: {
|
||||
name: nav.appName,
|
||||
version: nav.appVersion,
|
||||
cookie_enabled: nav.cookieEnabled,
|
||||
do_not_track: nav.doNotTrack
|
||||
},
|
||||
platform: nav.platform
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
url: location.pathname,
|
||||
referrer: document.referrer,
|
||||
screen: {
|
||||
width: screen.width,
|
||||
height: screen.height
|
||||
},
|
||||
window: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
},
|
||||
browser: {
|
||||
name: nav.appName,
|
||||
version: nav.appVersion,
|
||||
cookie_enabled: nav.cookieEnabled,
|
||||
do_not_track: nav.doNotTrack
|
||||
},
|
||||
platform: nav.platform
|
||||
};
|
||||
};
|
||||
// Single arg stops the compiler arity warning
|
||||
var _rawLog = function(msg) {
|
||||
var event = _getEvent.apply(this, arguments);
|
||||
|
||||
// Single arg stops the compiler arity warning
|
||||
var _rawLog = function(msg) {
|
||||
var event = _getEvent.apply(this, arguments);
|
||||
var data = { event: event };
|
||||
|
||||
var data = {event: event};
|
||||
// Add agent info if required
|
||||
if (_pageInfo !== "never") {
|
||||
if (!_sentPageInfo || _pageInfo === "per-entry") {
|
||||
_sentPageInfo = true;
|
||||
if (
|
||||
typeof event.screen === "undefined" &&
|
||||
typeof event.browser === "undefined"
|
||||
)
|
||||
_rawLog(_agentInfo())
|
||||
.level("PAGE")
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
// Add agent info if required
|
||||
if (_pageInfo !== 'never') {
|
||||
if (!_sentPageInfo || _pageInfo === 'per-entry') {
|
||||
_sentPageInfo = true;
|
||||
if (typeof event.screen === "undefined" &&
|
||||
typeof event.browser === "undefined")
|
||||
_rawLog(_agentInfo()).level('PAGE').send();
|
||||
}
|
||||
}
|
||||
if (_traceCode) {
|
||||
data.trace = _traceCode;
|
||||
}
|
||||
|
||||
if (_traceCode) {
|
||||
data.trace = _traceCode;
|
||||
}
|
||||
return {
|
||||
level: function(l) {
|
||||
// Don't log PAGE events to console
|
||||
// PAGE events are generated for the agentInfo function
|
||||
if (_print && typeof console !== "undefined" && l !== "PAGE") {
|
||||
var serialized = null;
|
||||
if (typeof XDomainRequest !== "undefined") {
|
||||
// We're using IE8/9
|
||||
serialized = data.trace + " " + data.event;
|
||||
}
|
||||
try {
|
||||
console[l.toLowerCase()].call(console, serialized || data);
|
||||
} catch (ex) {
|
||||
// IE compat fix
|
||||
console.log(serialized || data);
|
||||
}
|
||||
}
|
||||
data.level = l;
|
||||
|
||||
return {level: function(l) {
|
||||
// Don't log PAGE events to console
|
||||
// PAGE events are generated for the agentInfo function
|
||||
if (_print && typeof console !== "undefined" && l !== 'PAGE') {
|
||||
var serialized = null;
|
||||
if (typeof XDomainRequest !== "undefined") {
|
||||
// We're using IE8/9
|
||||
serialized = data.trace + ' ' + data.event;
|
||||
}
|
||||
try {
|
||||
console[l.toLowerCase()].call(console, (serialized || data));
|
||||
} catch (ex) {
|
||||
// IE compat fix
|
||||
console.log((serialized || data));
|
||||
}
|
||||
}
|
||||
data.level = l;
|
||||
return {
|
||||
send: function() {
|
||||
var cache = [];
|
||||
var serialized = JSON.stringify(data, function(key, value) {
|
||||
if (typeof value === "undefined") {
|
||||
return "undefined";
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
if (_indexOf(cache, value) !== -1) {
|
||||
// We've seen this object before;
|
||||
// return a placeholder instead to prevent
|
||||
// cycles
|
||||
return "<?>";
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
return {send: function() {
|
||||
var cache = [];
|
||||
var serialized = JSON.stringify(data, function(key, value) {
|
||||
|
||||
if (typeof value === "undefined") {
|
||||
return "undefined";
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
if (_indexOf(cache, value) !== -1) {
|
||||
// We've seen this object before;
|
||||
// return a placeholder instead to prevent
|
||||
// cycles
|
||||
return "<?>";
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
if (_active) {
|
||||
_backlog.push(serialized);
|
||||
} else {
|
||||
_apiCall(_token, serialized);
|
||||
}
|
||||
}};
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
if (options.catchall) {
|
||||
var oldHandler = window.onerror;
|
||||
var newHandler = function(msg, url, line) {
|
||||
_rawLog({error: msg, line: line, location: url}).level('ERROR').send();
|
||||
if (oldHandler) {
|
||||
return oldHandler(msg, url, line);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.onerror = newHandler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @expose */
|
||||
this.log = _rawLog;
|
||||
if (_active) {
|
||||
_backlog.push(serialized);
|
||||
} else {
|
||||
_apiCall(_token, serialized);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (options.catchall) {
|
||||
var oldHandler = window.onerror;
|
||||
var newHandler = function(msg, url, line) {
|
||||
_rawLog({ error: msg, line: line, location: url })
|
||||
.level("ERROR")
|
||||
.send();
|
||||
if (oldHandler) {
|
||||
return oldHandler(msg, url, line);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.onerror = newHandler;
|
||||
}
|
||||
|
||||
/** @expose */
|
||||
this.log = _rawLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single log object
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
* A single log object
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
function Logger(options) {
|
||||
var logger;
|
||||
var logger;
|
||||
|
||||
// Default values
|
||||
var dict = {
|
||||
ssl: true,
|
||||
catchall: false,
|
||||
trace: true,
|
||||
page_info: 'never',
|
||||
print: false,
|
||||
endpoint: null,
|
||||
token: null
|
||||
};
|
||||
// Default values
|
||||
var dict = {
|
||||
ssl: true,
|
||||
catchall: false,
|
||||
trace: true,
|
||||
page_info: "never",
|
||||
print: false,
|
||||
endpoint: null,
|
||||
token: null
|
||||
};
|
||||
|
||||
if (typeof options === "object")
|
||||
for (var k in options)
|
||||
dict[k] = options[k];
|
||||
else
|
||||
throw new Error("Invalid parameters for createLogStream()");
|
||||
if (typeof options === "object") for (var k in options) dict[k] = options[k];
|
||||
else throw new Error("Invalid parameters for createLogStream()");
|
||||
|
||||
if (dict.token === null) {
|
||||
throw new Error("Token not present.");
|
||||
} else {
|
||||
logger = new LogStream(dict);
|
||||
}
|
||||
if (dict.token === null) {
|
||||
throw new Error("Token not present.");
|
||||
} else {
|
||||
logger = new LogStream(dict);
|
||||
}
|
||||
|
||||
var _log = function(msg) {
|
||||
if (logger) {
|
||||
return logger.log.apply(this, arguments);
|
||||
} else
|
||||
throw new Error("You must call LE.init(...) first.");
|
||||
};
|
||||
var _log = function(msg) {
|
||||
if (logger) {
|
||||
return logger.log.apply(this, arguments);
|
||||
} else throw new Error("You must call LE.init(...) first.");
|
||||
};
|
||||
|
||||
// The public interface
|
||||
return {
|
||||
log: function() {
|
||||
_log.apply(this, arguments).level('LOG').send();
|
||||
},
|
||||
warn: function() {
|
||||
_log.apply(this, arguments).level('WARN').send();
|
||||
},
|
||||
error: function() {
|
||||
_log.apply(this, arguments).level('ERROR').send();
|
||||
},
|
||||
info: function() {
|
||||
_log.apply(this, arguments).level('INFO').send();
|
||||
}
|
||||
};
|
||||
// The public interface
|
||||
return {
|
||||
log: function() {
|
||||
_log
|
||||
.apply(this, arguments)
|
||||
.level("LOG")
|
||||
.send();
|
||||
},
|
||||
warn: function() {
|
||||
_log
|
||||
.apply(this, arguments)
|
||||
.level("WARN")
|
||||
.send();
|
||||
},
|
||||
error: function() {
|
||||
_log
|
||||
.apply(this, arguments)
|
||||
.level("ERROR")
|
||||
.send();
|
||||
},
|
||||
info: function() {
|
||||
_log
|
||||
.apply(this, arguments)
|
||||
.level("INFO")
|
||||
.send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Array of Logger elements
|
||||
var loggers = {};
|
||||
|
||||
var _getLogger = function(name) {
|
||||
if (!loggers.hasOwnProperty(name))
|
||||
throw new Error("Invalid name for logStream");
|
||||
if (!loggers.hasOwnProperty(name))
|
||||
throw new Error("Invalid name for logStream");
|
||||
|
||||
return loggers[name];
|
||||
return loggers[name];
|
||||
};
|
||||
|
||||
var _createLogStream = function(options) {
|
||||
if (typeof options.name !== "string")
|
||||
throw new Error("Name not present.");
|
||||
else if (loggers.hasOwnProperty(options.name))
|
||||
throw new Error("A logger with that name already exists!");
|
||||
loggers[options.name] = new Logger(options);
|
||||
var _createLogStream = function(options) {
|
||||
if (typeof options.name !== "string") throw new Error("Name not present.");
|
||||
else if (loggers.hasOwnProperty(options.name))
|
||||
throw new Error("A logger with that name already exists!");
|
||||
loggers[options.name] = new Logger(options);
|
||||
|
||||
return true;
|
||||
return true;
|
||||
};
|
||||
|
||||
var _deprecatedInit = function(options) {
|
||||
var dict = {
|
||||
name : "default"
|
||||
};
|
||||
var dict = {
|
||||
name: "default"
|
||||
};
|
||||
|
||||
if (typeof options === "object")
|
||||
for (var k in options)
|
||||
dict[k] = options[k];
|
||||
else if (typeof options === "string")
|
||||
dict.token = options;
|
||||
else
|
||||
throw new Error("Invalid parameters for init()");
|
||||
if (typeof options === "object") for (var k in options) dict[k] = options[k];
|
||||
else if (typeof options === "string") dict.token = options;
|
||||
else throw new Error("Invalid parameters for init()");
|
||||
|
||||
return _createLogStream(dict);
|
||||
return _createLogStream(dict);
|
||||
};
|
||||
|
||||
var _destroyLogStream = function(name) {
|
||||
if (typeof name === 'undefined'){
|
||||
name = 'default';
|
||||
}
|
||||
if (typeof name === "undefined") {
|
||||
name = "default";
|
||||
}
|
||||
|
||||
delete loggers[name];
|
||||
delete loggers[name];
|
||||
};
|
||||
|
||||
// The public interface
|
||||
export default {
|
||||
init: _deprecatedInit,
|
||||
createLogStream: _createLogStream,
|
||||
to: _getLogger,
|
||||
destroy: _destroyLogStream,
|
||||
log: function() {
|
||||
for (var k in loggers)
|
||||
loggers[k].log.apply(this, arguments);
|
||||
},
|
||||
warn: function() {
|
||||
for (var k in loggers)
|
||||
loggers[k].warn.apply(this, arguments);
|
||||
},
|
||||
error: function() {
|
||||
for (var k in loggers)
|
||||
loggers[k].error.apply(this, arguments);
|
||||
},
|
||||
info: function() {
|
||||
for (var k in loggers)
|
||||
loggers[k].info.apply(this, arguments);
|
||||
}
|
||||
init: _deprecatedInit,
|
||||
createLogStream: _createLogStream,
|
||||
to: _getLogger,
|
||||
destroy: _destroyLogStream,
|
||||
log: function() {
|
||||
for (var k in loggers) loggers[k].log.apply(this, arguments);
|
||||
},
|
||||
warn: function() {
|
||||
for (var k in loggers) loggers[k].warn.apply(this, arguments);
|
||||
},
|
||||
error: function() {
|
||||
for (var k in loggers) loggers[k].error.apply(this, arguments);
|
||||
},
|
||||
info: function() {
|
||||
for (var k in loggers) loggers[k].info.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import Raven from 'raven-js';
|
||||
import Raven from "raven-js";
|
||||
|
||||
function init() {
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
const opts = {};
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
const opts = {};
|
||||
|
||||
if(typeof __webpack_hash__ !== 'undefined') {
|
||||
opts['release'] = __webpack_hash__; // eslint-disable-line no-undef
|
||||
}
|
||||
|
||||
Raven
|
||||
.config('https://50006e5d91894f508dd288bbbf4585a6@sentry.io/185303', opts)
|
||||
.install();
|
||||
if (typeof __webpack_hash__ !== "undefined") {
|
||||
opts["release"] = __webpack_hash__; // eslint-disable-line no-undef
|
||||
}
|
||||
|
||||
Raven.config(
|
||||
"https://50006e5d91894f508dd288bbbf4585a6@sentry.io/185303",
|
||||
opts
|
||||
).install();
|
||||
}
|
||||
}
|
||||
|
||||
export default {init};
|
||||
export default { init };
|
||||
|
|
|
@ -8,9 +8,9 @@ const bitArray = {
|
|||
* slice until the end of the array.
|
||||
* @return {bitArray} The requested slice.
|
||||
*/
|
||||
bitSlice: function (a, bstart, bend) {
|
||||
a = bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
|
||||
return (bend === undefined) ? a : bitArray.clamp(a, bend-bstart);
|
||||
bitSlice: function(a, bstart, bend) {
|
||||
a = bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
|
||||
return bend === undefined ? a : bitArray.clamp(a, bend - bstart);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -23,15 +23,17 @@ const bitArray = {
|
|||
extract: function(a, bstart, blength) {
|
||||
// FIXME: this Math.floor is not necessary at all, but for some reason
|
||||
// seems to suppress a bug in the Chromium JIT.
|
||||
var x, sh = Math.floor((-bstart-blength) & 31);
|
||||
if ((bstart + blength - 1 ^ bstart) & -32) {
|
||||
var x,
|
||||
sh = Math.floor((-bstart - blength) & 31);
|
||||
if (((bstart + blength - 1) ^ bstart) & -32) {
|
||||
// it crosses a boundary
|
||||
x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
|
||||
x =
|
||||
(a[(bstart / 32) | 0] << (32 - sh)) ^ (a[(bstart / 32 + 1) | 0] >>> sh);
|
||||
} else {
|
||||
// within a single word
|
||||
x = a[bstart/32|0] >>> sh;
|
||||
x = a[(bstart / 32) | 0] >>> sh;
|
||||
}
|
||||
return x & ((1<<blength) - 1);
|
||||
return x & ((1 << blength) - 1);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -40,16 +42,22 @@ const bitArray = {
|
|||
* @param {bitArray} a2 The second array.
|
||||
* @return {bitArray} The concatenation of a1 and a2.
|
||||
*/
|
||||
concat: function (a1, a2) {
|
||||
concat: function(a1, a2) {
|
||||
if (a1.length === 0 || a2.length === 0) {
|
||||
return a1.concat(a2);
|
||||
}
|
||||
|
||||
var last = a1[a1.length-1], shift = bitArray.getPartial(last);
|
||||
var last = a1[a1.length - 1],
|
||||
shift = bitArray.getPartial(last);
|
||||
if (shift === 32) {
|
||||
return a1.concat(a2);
|
||||
} else {
|
||||
return bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
|
||||
return bitArray._shiftRight(
|
||||
a2,
|
||||
shift,
|
||||
last | 0,
|
||||
a1.slice(0, a1.length - 1)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -58,11 +66,14 @@ const bitArray = {
|
|||
* @param {bitArray} a The array.
|
||||
* @return {Number} The length of a, in bits.
|
||||
*/
|
||||
bitLength: function (a) {
|
||||
var l = a.length, x;
|
||||
if (l === 0) { return 0; }
|
||||
bitLength: function(a) {
|
||||
var l = a.length,
|
||||
x;
|
||||
if (l === 0) {
|
||||
return 0;
|
||||
}
|
||||
x = a[l - 1];
|
||||
return (l-1) * 32 + bitArray.getPartial(x);
|
||||
return (l - 1) * 32 + bitArray.getPartial(x);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -71,13 +82,15 @@ const bitArray = {
|
|||
* @param {Number} len The length to truncate to, in bits.
|
||||
* @return {bitArray} A new array, truncated to len bits.
|
||||
*/
|
||||
clamp: function (a, len) {
|
||||
if (a.length * 32 < len) { return a; }
|
||||
clamp: function(a, len) {
|
||||
if (a.length * 32 < len) {
|
||||
return a;
|
||||
}
|
||||
a = a.slice(0, Math.ceil(len / 32));
|
||||
var l = a.length;
|
||||
len &= 31;
|
||||
if (l > 0 && len) {
|
||||
a[l-1] = bitArray.partial(len, a[l-1] & (0x80000000 >> (len-1)), 1);
|
||||
a[l - 1] = bitArray.partial(len, a[l - 1] & (0x80000000 >> (len - 1)), 1);
|
||||
}
|
||||
return a;
|
||||
},
|
||||
|
@ -89,9 +102,11 @@ const bitArray = {
|
|||
* @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
|
||||
* @return {Number} The partial word.
|
||||
*/
|
||||
partial: function (len, x, _end) {
|
||||
if (len === 32) { return x; }
|
||||
return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
|
||||
partial: function(len, x, _end) {
|
||||
if (len === 32) {
|
||||
return x;
|
||||
}
|
||||
return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -99,8 +114,8 @@ const bitArray = {
|
|||
* @param {Number} x The partial word.
|
||||
* @return {Number} The number of bits used by the partial word.
|
||||
*/
|
||||
getPartial: function (x) {
|
||||
return Math.round(x/0x10000000000) || 32;
|
||||
getPartial: function(x) {
|
||||
return Math.round(x / 0x10000000000) || 32;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -109,15 +124,16 @@ const bitArray = {
|
|||
* @param {bitArray} b The second array.
|
||||
* @return {boolean} true if a == b; false otherwise.
|
||||
*/
|
||||
equal: function (a, b) {
|
||||
equal: function(a, b) {
|
||||
if (bitArray.bitLength(a) !== bitArray.bitLength(b)) {
|
||||
return false;
|
||||
}
|
||||
var x = 0, i;
|
||||
for (i=0; i<a.length; i++) {
|
||||
x |= a[i]^b[i];
|
||||
var x = 0,
|
||||
i;
|
||||
for (i = 0; i < a.length; i++) {
|
||||
x |= a[i] ^ b[i];
|
||||
}
|
||||
return (x === 0);
|
||||
return x === 0;
|
||||
},
|
||||
|
||||
/** Shift an array right.
|
||||
|
@ -127,9 +143,13 @@ const bitArray = {
|
|||
* @param {bitArray} [out=[]] An array to prepend to the output.
|
||||
* @private
|
||||
*/
|
||||
_shiftRight: function (a, shift, carry, out) {
|
||||
var i, last2=0, shift2;
|
||||
if (out === undefined) { out = []; }
|
||||
_shiftRight: function(a, shift, carry, out) {
|
||||
var i,
|
||||
last2 = 0,
|
||||
shift2;
|
||||
if (out === undefined) {
|
||||
out = [];
|
||||
}
|
||||
|
||||
for (; shift >= 32; shift -= 32) {
|
||||
out.push(carry);
|
||||
|
@ -139,21 +159,27 @@ const bitArray = {
|
|||
return out.concat(a);
|
||||
}
|
||||
|
||||
for (i=0; i<a.length; i++) {
|
||||
out.push(carry | (a[i]>>>shift));
|
||||
carry = a[i] << (32-shift);
|
||||
for (i = 0; i < a.length; i++) {
|
||||
out.push(carry | (a[i] >>> shift));
|
||||
carry = a[i] << (32 - shift);
|
||||
}
|
||||
last2 = a.length ? a[a.length-1] : 0;
|
||||
last2 = a.length ? a[a.length - 1] : 0;
|
||||
shift2 = bitArray.getPartial(last2);
|
||||
out.push(bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
|
||||
out.push(
|
||||
bitArray.partial(
|
||||
(shift + shift2) & 31,
|
||||
shift + shift2 > 32 ? carry : out.pop(),
|
||||
1
|
||||
)
|
||||
);
|
||||
return out;
|
||||
},
|
||||
|
||||
/** xor a block of 4 words together.
|
||||
* @private
|
||||
*/
|
||||
_xor4: function(x,y) {
|
||||
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
|
||||
_xor4: function(x, y) {
|
||||
return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
|
||||
},
|
||||
|
||||
/** byteswap a word array inplace.
|
||||
|
@ -162,7 +188,9 @@ const bitArray = {
|
|||
* @return {bitArray} byteswapped array
|
||||
*/
|
||||
byteswapM: function(a) {
|
||||
var i, v, m = 0xff00;
|
||||
var i,
|
||||
v,
|
||||
m = 0xff00;
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
v = a[i];
|
||||
a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
|
||||
|
@ -173,18 +201,20 @@ const bitArray = {
|
|||
|
||||
export default {
|
||||
fromBytes: function(bytes) {
|
||||
var out = [], i, tmp=0;
|
||||
for (i=0; i<bytes.length; i++) {
|
||||
var out = [],
|
||||
i,
|
||||
tmp = 0;
|
||||
for (i = 0; i < bytes.length; i++) {
|
||||
tmp = (tmp << 8) | bytes[i];
|
||||
if ((i&3) === 3) {
|
||||
if ((i & 3) === 3) {
|
||||
out.push(tmp);
|
||||
tmp = 0;
|
||||
}
|
||||
}
|
||||
if (i&3) {
|
||||
out.push(bitArray.partial(8*(i&3), tmp));
|
||||
if (i & 3) {
|
||||
out.push(bitArray.partial(8 * (i & 3), tmp));
|
||||
}
|
||||
return out;
|
||||
},
|
||||
...bitArray
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
const Uint64BE = require('int64-buffer').Uint64BE
|
||||
const Uint64BE = require("int64-buffer").Uint64BE;
|
||||
|
||||
export function formatForMsg(msg) {
|
||||
return {bstart: 0, bend: 15}
|
||||
return { bstart: 0, bend: 15 };
|
||||
}
|
||||
|
||||
export function formatMsgDec(msg) {
|
||||
const {bstart, bend} = formatForMsg(msg)
|
||||
const uint = Uint64BE(msg[1])
|
||||
var tt = "0"+uint.toString(2);
|
||||
tt = tt.substring(0, tt.length - (63-bend))
|
||||
tt = tt.substring(tt.length - (bend-bstart) - 1)
|
||||
const { bstart, bend } = formatForMsg(msg);
|
||||
const uint = Uint64BE(msg[1]);
|
||||
var tt = "0" + uint.toString(2);
|
||||
tt = tt.substring(0, tt.length - (63 - bend));
|
||||
tt = tt.substring(tt.length - (bend - bstart) - 1);
|
||||
return [msg[0], parseInt(tt, 2)];
|
||||
}
|
||||
|
||||
|
@ -20,17 +20,17 @@ export function uint64BEToHex(int64) {
|
|||
export function int64BufferToPrettyHexStr(buffer) {
|
||||
const uint = Uint64BE(buffer);
|
||||
let hex = uint.toString(16);
|
||||
if(hex.length === 1) hex = '0' + hex;
|
||||
if (hex.length === 1) hex = "0" + hex;
|
||||
let hexParts = hex.match(/.{1,2}/g);
|
||||
|
||||
return hexParts.join(" ")
|
||||
return hexParts.join(" ");
|
||||
}
|
||||
|
||||
export function formatMsgHex(msg) {
|
||||
const uint = Uint64BE(msg[1]);
|
||||
let hex = uint.toString(16);
|
||||
if(hex.length === 1) hex = '0' + hex;
|
||||
let hexParts = hex.match(/.{1,2}/g);
|
||||
const uint = Uint64BE(msg[1]);
|
||||
let hex = uint.toString(16);
|
||||
if (hex.length === 1) hex = "0" + hex;
|
||||
let hexParts = hex.match(/.{1,2}/g);
|
||||
|
||||
return [msg[0], hexParts.join(" ")]
|
||||
}
|
||||
return [msg[0], hexParts.join(" ")];
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
export default class BoardUnit {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.attributes = {};
|
||||
this.comment = null
|
||||
}
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.attributes = {};
|
||||
this.comment = null;
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
text() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,12 +421,9 @@ export default class DBC {
|
|||
messageId = parseInt(messageId, 10);
|
||||
const msg = messages.get(messageId);
|
||||
if (msg === undefined) {
|
||||
warnings.push(`failed to parse signal comment on line ${i + 1} -- ${
|
||||
line
|
||||
}:
|
||||
message id ${
|
||||
messageId
|
||||
} does not exist prior to this line`);
|
||||
warnings.push(`failed to parse signal comment on line ${i +
|
||||
1} -- ${line}:
|
||||
message id ${messageId} does not exist prior to this line`);
|
||||
continue;
|
||||
}
|
||||
const signal = msg.signals[signalName];
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
function findTimeIndex(entries, time) {
|
||||
return entries.findIndex((e) => e.time >= time);
|
||||
return entries.findIndex(e => e.time >= time);
|
||||
}
|
||||
|
||||
function findRelativeTimeIndex(entries, relTime) {
|
||||
return entries.findIndex((e) => e.relTime >= relTime);
|
||||
return entries.findIndex(e => e.relTime >= relTime);
|
||||
}
|
||||
|
||||
function findSegmentIndices(entries, [segmentTimeLow, segmentTimeHi], isRelative) {
|
||||
/*
|
||||
function findSegmentIndices(
|
||||
entries,
|
||||
[segmentTimeLow, segmentTimeHi],
|
||||
isRelative
|
||||
) {
|
||||
/*
|
||||
Finds pair of indices (inclusive, exclusive) within entries array
|
||||
whose timestamps match segmentTimeLow and segmentTimeHi.
|
||||
if isRelative === true, then the segment times
|
||||
|
@ -16,15 +20,19 @@ function findSegmentIndices(entries, [segmentTimeLow, segmentTimeHi], isRelative
|
|||
Returns `[segmentIdxLow, segmentIdxHigh]`
|
||||
(inclusive, exclusive)
|
||||
*/
|
||||
let timeIndexFunc = (isRelative === true ? findRelativeTimeIndex : findTimeIndex);
|
||||
let timeIndexFunc =
|
||||
isRelative === true ? findRelativeTimeIndex : findTimeIndex;
|
||||
|
||||
const segmentIdxLow = timeIndexFunc(entries, segmentTimeLow);
|
||||
const segmentIdxLow = timeIndexFunc(entries, segmentTimeLow);
|
||||
|
||||
const upperSegments = entries.slice(segmentIdxLow);
|
||||
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
|
||||
const segmentIdxHi = (upperSegmentIdxHi >= 0 ? upperSegmentIdxHi + segmentIdxLow + 1 : entries.length - 1)
|
||||
const upperSegments = entries.slice(segmentIdxLow);
|
||||
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
|
||||
const segmentIdxHi =
|
||||
upperSegmentIdxHi >= 0
|
||||
? upperSegmentIdxHi + segmentIdxLow + 1
|
||||
: entries.length - 1;
|
||||
|
||||
return [segmentIdxLow, Math.min(segmentIdxHi, entries.length - 1)]
|
||||
return [segmentIdxLow, Math.min(segmentIdxHi, entries.length - 1)];
|
||||
}
|
||||
|
||||
export default {findTimeIndex, findRelativeTimeIndex, findSegmentIndices};
|
||||
export default { findTimeIndex, findRelativeTimeIndex, findSegmentIndices };
|
||||
|
|
|
@ -1,56 +1,63 @@
|
|||
export default class Frame {
|
||||
constructor({name,
|
||||
id = 0,
|
||||
size = 0,
|
||||
transmitters = [],
|
||||
extended = 0,
|
||||
comment = null,
|
||||
signals = {}}) {
|
||||
Object.assign(this, {name,
|
||||
id,
|
||||
size,
|
||||
transmitters,
|
||||
extended,
|
||||
comment,
|
||||
signals})
|
||||
constructor({
|
||||
name,
|
||||
id = 0,
|
||||
size = 0,
|
||||
transmitters = [],
|
||||
extended = 0,
|
||||
comment = null,
|
||||
signals = {}
|
||||
}) {
|
||||
Object.assign(this, {
|
||||
name,
|
||||
id,
|
||||
size,
|
||||
transmitters,
|
||||
extended,
|
||||
comment,
|
||||
signals
|
||||
});
|
||||
}
|
||||
|
||||
nextNewTransmitterName() {
|
||||
let txNum = 1,
|
||||
txName;
|
||||
do {
|
||||
txName = "NEW_TRANSMITTER_" + txNum;
|
||||
txNum++;
|
||||
} while (this.transmitters.indexOf(txName) !== -1);
|
||||
|
||||
return txName;
|
||||
}
|
||||
|
||||
addTransmitter() {
|
||||
const txName = this.nextNewTransmitterName();
|
||||
this.transmitters.push(txName);
|
||||
return txName;
|
||||
}
|
||||
|
||||
header() {
|
||||
return (
|
||||
`BO_ ${this.id} ${this.name}: ${this.size} ` +
|
||||
`${this.transmitters[0] || "XXX"}`
|
||||
);
|
||||
}
|
||||
|
||||
text() {
|
||||
const signals = Object.values(this.signals)
|
||||
.map(signal => " " + signal.text()) // indent
|
||||
.join("\n");
|
||||
|
||||
if (signals.length > 0) {
|
||||
return this.header() + "\n" + signals;
|
||||
} else {
|
||||
return this.header();
|
||||
}
|
||||
}
|
||||
|
||||
nextNewTransmitterName() {
|
||||
let txNum = 1, txName;
|
||||
do {
|
||||
txName = 'NEW_TRANSMITTER_' + txNum;
|
||||
txNum++;
|
||||
} while(this.transmitters.indexOf(txName) !== -1);
|
||||
copy() {
|
||||
const copy = Object.assign(Object.create(this), this);
|
||||
|
||||
return txName;
|
||||
}
|
||||
|
||||
addTransmitter() {
|
||||
const txName = this.nextNewTransmitterName();
|
||||
this.transmitters.push(txName);
|
||||
return txName;
|
||||
}
|
||||
|
||||
header() {
|
||||
return `BO_ ${this.id} ${this.name}: ${this.size} ` +
|
||||
`${this.transmitters[0] || 'XXX'}`;
|
||||
}
|
||||
|
||||
text() {
|
||||
const signals = Object.values(this.signals)
|
||||
.map((signal) => " " + signal.text()) // indent
|
||||
.join("\n");
|
||||
|
||||
if(signals.length > 0) {
|
||||
return this.header() + "\n" + signals;
|
||||
} else {
|
||||
return this.header();
|
||||
}
|
||||
}
|
||||
|
||||
copy() {
|
||||
const copy = Object.assign(Object.create(this), this);
|
||||
|
||||
return copy;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,141 +1,173 @@
|
|||
import ArrayUtils from '../utils/array';
|
||||
import {CAN_GRAPH_MAX_POINTS} from '../config';
|
||||
import ArrayUtils from "../utils/array";
|
||||
import { CAN_GRAPH_MAX_POINTS } from "../config";
|
||||
|
||||
function _calcGraphData(msg, signalUid, firstCanTime) {
|
||||
if(!msg) return null;
|
||||
if (!msg) return null;
|
||||
|
||||
const signal = Object.values(msg.frame.signals).find((s) => s.uid === signalUid);
|
||||
if(!signal) {
|
||||
console.warn('_calcGraphData: no signal', signalUid, msg)
|
||||
return null;
|
||||
}
|
||||
let samples = [];
|
||||
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
|
||||
const signal = Object.values(msg.frame.signals).find(
|
||||
s => s.uid === signalUid
|
||||
);
|
||||
if (!signal) {
|
||||
console.warn("_calcGraphData: no signal", signalUid, msg);
|
||||
return null;
|
||||
}
|
||||
let samples = [];
|
||||
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
|
||||
|
||||
if(skip === 0){
|
||||
samples = msg.entries;
|
||||
} else {
|
||||
for(let i = 0; i < msg.entries.length; i += skip) {
|
||||
samples.push(msg.entries[i]);
|
||||
}
|
||||
// Always include last message entry, which faciliates graphData comparison
|
||||
samples.push(msg.entries[msg.entries.length - 1]);
|
||||
if (skip === 0) {
|
||||
samples = msg.entries;
|
||||
} else {
|
||||
for (let i = 0; i < msg.entries.length; i += skip) {
|
||||
samples.push(msg.entries[i]);
|
||||
}
|
||||
return samples.filter((e) => e.signals[signal.name] !== undefined)
|
||||
.map((entry) => {
|
||||
return {x: entry.time,
|
||||
relTime: entry.relTime,
|
||||
y: entry.signals[signal.name],
|
||||
unit: signal.unit,
|
||||
color: `rgba(${signal.colors.join(",")}, 0.5)`,
|
||||
signalName: signal.name,
|
||||
signalUid}
|
||||
// Always include last message entry, which faciliates graphData comparison
|
||||
samples.push(msg.entries[msg.entries.length - 1]);
|
||||
}
|
||||
return samples
|
||||
.filter(e => e.signals[signal.name] !== undefined)
|
||||
.map(entry => {
|
||||
return {
|
||||
x: entry.time,
|
||||
relTime: entry.relTime,
|
||||
y: entry.signals[signal.name],
|
||||
unit: signal.unit,
|
||||
color: `rgba(${signal.colors.join(",")}, 0.5)`,
|
||||
signalName: signal.name,
|
||||
signalUid
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function appendNewGraphData(plottedSignals, graphData, messages, firstCanTime) {
|
||||
const messagesPerPlot = plottedSignals.map((plottedMessages) =>
|
||||
plottedMessages.reduce((messages,
|
||||
{messageId, signalUid}) => {
|
||||
messages.push(messageId);
|
||||
return messages;
|
||||
}, [])
|
||||
const messagesPerPlot = plottedSignals.map(plottedMessages =>
|
||||
plottedMessages.reduce((messages, { messageId, signalUid }) => {
|
||||
messages.push(messageId);
|
||||
return messages;
|
||||
}, [])
|
||||
);
|
||||
|
||||
const extendedPlots = messagesPerPlot
|
||||
.map((plottedMessageIds, index) => {
|
||||
return { plottedMessageIds, index };
|
||||
}) // preserve index so we can look up graphData
|
||||
.filter(({ plottedMessageIds, index }) => {
|
||||
if (index < graphData.length) {
|
||||
let maxGraphTime = 0;
|
||||
const { series } = graphData[index];
|
||||
if (series.length > 0) {
|
||||
maxGraphTime = series[graphData[index].series.length - 1].relTime;
|
||||
}
|
||||
|
||||
return plottedMessageIds.some(
|
||||
messageId =>
|
||||
(messages[messageId].entries.length > 0 && series.length === 0) ||
|
||||
messages[messageId].entries.some(e => e.relTime > maxGraphTime)
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(({ plottedMessageIds, index }) => {
|
||||
plottedMessageIds = plottedMessageIds.reduce((arr, messageId) => {
|
||||
if (arr.indexOf(messageId) === -1) {
|
||||
arr.push(messageId);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
return { plottedMessageIds, index };
|
||||
});
|
||||
|
||||
extendedPlots.forEach(({ plottedMessageIds, index }) => {
|
||||
const signalUidsByMessageId = plottedSignals[index].reduce(
|
||||
(obj, { messageId, signalUid }) => {
|
||||
if (!obj[messageId]) {
|
||||
obj[messageId] = [];
|
||||
}
|
||||
obj[messageId].push(signalUid);
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const { series } = graphData[index];
|
||||
const graphDataMaxMessageTimes = plottedMessageIds.reduce(
|
||||
(obj, messageId) => {
|
||||
const signalUids = signalUidsByMessageId[messageId];
|
||||
const maxIndex = ArrayUtils.findIndexRight(series, entry => {
|
||||
return signalUids.indexOf(entry.signalUid) !== -1;
|
||||
});
|
||||
if (maxIndex) {
|
||||
obj[messageId] = series[maxIndex].relTime;
|
||||
} else if (series.length > 0) {
|
||||
obj[messageId] = series[series.length - 1].relTime;
|
||||
} else {
|
||||
// Graph data is empty
|
||||
obj[messageId] = -1;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
let newGraphData = [];
|
||||
plottedMessageIds
|
||||
.map(messageId => {
|
||||
return { messageId, entries: messages[messageId].entries };
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
{ messageId, entries } // Filter to only messages with stale graphData
|
||||
) =>
|
||||
entries[entries.length - 1].relTime >
|
||||
graphDataMaxMessageTimes[messageId]
|
||||
)
|
||||
.forEach(({ messageId, entries }) => {
|
||||
// Compute and append new graphData
|
||||
let firstNewEntryIdx = entries.findIndex(
|
||||
entry => entry.relTime > graphDataMaxMessageTimes[messageId]
|
||||
);
|
||||
|
||||
const extendedPlots = messagesPerPlot
|
||||
.map((plottedMessageIds, index) => {return {plottedMessageIds, index}}) // preserve index so we can look up graphData
|
||||
.filter(({plottedMessageIds, index}) => {
|
||||
if(index < graphData.length) {
|
||||
let maxGraphTime = 0;
|
||||
const { series } = graphData[index];
|
||||
if(series.length > 0) {
|
||||
maxGraphTime = series[graphData[index].series.length - 1].relTime;
|
||||
}
|
||||
const newEntries = entries.slice(firstNewEntryIdx);
|
||||
signalUidsByMessageId[messageId].forEach(signalUid => {
|
||||
const signalGraphData = _calcGraphData(
|
||||
{
|
||||
...messages[messageId],
|
||||
entries: newEntries
|
||||
},
|
||||
signalUid,
|
||||
firstCanTime
|
||||
);
|
||||
|
||||
return plottedMessageIds.some((messageId) =>
|
||||
(messages[messageId].entries.length > 0 && series.length === 0)
|
||||
||
|
||||
messages[messageId].entries.some((e) => e.relTime > maxGraphTime));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).map(({plottedMessageIds, index}) => {
|
||||
plottedMessageIds = plottedMessageIds.reduce((arr, messageId) => {
|
||||
if(arr.indexOf(messageId) === -1) {
|
||||
arr.push(messageId);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
return {plottedMessageIds, index};
|
||||
});
|
||||
|
||||
extendedPlots.forEach(({plottedMessageIds, index}) => {
|
||||
const signalUidsByMessageId = plottedSignals[index].reduce((obj, {messageId, signalUid}) => {
|
||||
if(!obj[messageId]) {
|
||||
obj[messageId] = []
|
||||
}
|
||||
obj[messageId].push(signalUid);
|
||||
return obj;
|
||||
}, {});
|
||||
const { series } = graphData[index];
|
||||
const graphDataMaxMessageTimes = plottedMessageIds.reduce((obj, messageId) => {
|
||||
const signalUids = signalUidsByMessageId[messageId];
|
||||
const maxIndex = ArrayUtils.findIndexRight(series, (entry) => {
|
||||
return signalUids.indexOf(entry.signalUid) !== -1
|
||||
});
|
||||
if(maxIndex) {
|
||||
obj[messageId] = series[maxIndex].relTime;
|
||||
} else if(series.length > 0) {
|
||||
obj[messageId] = series[series.length - 1].relTime;
|
||||
} else {
|
||||
// Graph data is empty
|
||||
obj[messageId] = -1;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
let newGraphData = [];
|
||||
plottedMessageIds.map((messageId) => {
|
||||
return { messageId, entries: messages[messageId].entries }
|
||||
}).filter(({messageId, entries}) => // Filter to only messages with stale graphData
|
||||
entries[entries.length - 1].relTime > graphDataMaxMessageTimes[messageId])
|
||||
.forEach(({messageId, entries}) => { // Compute and append new graphData
|
||||
let firstNewEntryIdx = entries.findIndex((entry) =>
|
||||
entry.relTime > graphDataMaxMessageTimes[messageId]);
|
||||
|
||||
const newEntries = entries.slice(firstNewEntryIdx);
|
||||
signalUidsByMessageId[messageId].forEach((signalUid) => {
|
||||
const signalGraphData = _calcGraphData({...messages[messageId],
|
||||
entries: newEntries},
|
||||
signalUid,
|
||||
firstCanTime);
|
||||
|
||||
newGraphData = newGraphData.concat(signalGraphData);
|
||||
});
|
||||
newGraphData = newGraphData.concat(signalGraphData);
|
||||
});
|
||||
});
|
||||
|
||||
const messageIdOutOfBounds = (
|
||||
series.length > 0
|
||||
&& plottedMessageIds.find((messageId) =>
|
||||
messages[messageId].entries.length > 0
|
||||
&& series[0].relTime < messages[messageId].entries[0].relTime));
|
||||
graphData[index] = {
|
||||
series: graphData[index].series.concat(newGraphData),
|
||||
updated: Date.now()
|
||||
};
|
||||
const messageIdOutOfBounds =
|
||||
series.length > 0 &&
|
||||
plottedMessageIds.find(
|
||||
messageId =>
|
||||
messages[messageId].entries.length > 0 &&
|
||||
series[0].relTime < messages[messageId].entries[0].relTime
|
||||
);
|
||||
graphData[index] = {
|
||||
series: graphData[index].series.concat(newGraphData),
|
||||
updated: Date.now()
|
||||
};
|
||||
|
||||
if(messageIdOutOfBounds) {
|
||||
const graphDataLowerBound = graphData[index].series.findIndex(
|
||||
(e) => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime);
|
||||
if (messageIdOutOfBounds) {
|
||||
const graphDataLowerBound = graphData[index].series.findIndex(
|
||||
e => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime
|
||||
);
|
||||
|
||||
if(graphDataLowerBound) {
|
||||
graphData[index].series = graphData[index].series.slice(graphDataLowerBound);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (graphDataLowerBound) {
|
||||
graphData[index].series = graphData[index].series.slice(
|
||||
graphDataLowerBound
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [...graphData];
|
||||
return [...graphData];
|
||||
}
|
||||
|
||||
export default {_calcGraphData, appendNewGraphData};
|
||||
export default { _calcGraphData, appendNewGraphData };
|
||||
|
|
|
@ -1,51 +1,67 @@
|
|||
import React from 'react';
|
||||
import {StyleSheet, css} from 'aphrodite/no-important';
|
||||
import React from "react";
|
||||
import { StyleSheet, css } from "aphrodite/no-important";
|
||||
|
||||
import * as ObjectUtils from '../utils/object';
|
||||
import * as ObjectUtils from "../utils/object";
|
||||
|
||||
function createImageComponent(source, alt, styles) {
|
||||
if(styles === undefined) {
|
||||
styles = []
|
||||
} else if(!Array.isArray(styles)) {
|
||||
styles = [styles];
|
||||
if (styles === undefined) {
|
||||
styles = [];
|
||||
} else if (!Array.isArray(styles)) {
|
||||
styles = [styles];
|
||||
}
|
||||
|
||||
return props => {
|
||||
let localStyles = styles.slice();
|
||||
if (Array.isArray(props.styles)) {
|
||||
localStyles = localStyles.concat(props.styles);
|
||||
// filter 'styles' from props, which is passed via spread to <img> tag.
|
||||
props = ObjectUtils.fromArray(
|
||||
Object.entries(props).filter(([k, v]) => k !== "styles")
|
||||
);
|
||||
}
|
||||
|
||||
return (props) => {
|
||||
let localStyles = styles.slice();
|
||||
if(Array.isArray(props.styles)) {
|
||||
localStyles = localStyles.concat(props.styles);
|
||||
// filter 'styles' from props, which is passed via spread to <img> tag.
|
||||
props = ObjectUtils.fromArray(Object.entries(props).filter(([k,v]) => k !== 'styles'));
|
||||
}
|
||||
|
||||
return <img src={source} className={css(...localStyles)} alt={alt} {...props} />
|
||||
};
|
||||
return (
|
||||
<img src={source} className={css(...localStyles)} alt={alt} {...props} />
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
materialIcon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
pointer: {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
materialIcon: {
|
||||
width: 24,
|
||||
height: 24
|
||||
},
|
||||
pointer: {
|
||||
cursor: "pointer"
|
||||
}
|
||||
});
|
||||
|
||||
const leftArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
|
||||
'Left arrow',
|
||||
Styles.materialIcon);
|
||||
const rightArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_right_black_24dp.png",
|
||||
'Right arrow',
|
||||
Styles.materialIcon);
|
||||
const leftArrow = createImageComponent(
|
||||
process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
|
||||
"Left arrow",
|
||||
Styles.materialIcon
|
||||
);
|
||||
const rightArrow = createImageComponent(
|
||||
process.env.PUBLIC_URL + "/img/ic_arrow_right_black_24dp.png",
|
||||
"Right arrow",
|
||||
Styles.materialIcon
|
||||
);
|
||||
|
||||
const downArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_drop_down_black_24dp.png",
|
||||
'Down arrow',
|
||||
Styles.materialIcon)
|
||||
const downArrow = createImageComponent(
|
||||
process.env.PUBLIC_URL + "/img/ic_arrow_drop_down_black_24dp.png",
|
||||
"Down arrow",
|
||||
Styles.materialIcon
|
||||
);
|
||||
|
||||
const clear = createImageComponent(process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
|
||||
'Clear',
|
||||
[Styles.materialIcon, Styles.pointer]);
|
||||
const clear = createImageComponent(
|
||||
process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
|
||||
"Clear",
|
||||
[Styles.materialIcon, Styles.pointer]
|
||||
);
|
||||
|
||||
const panda = createImageComponent(process.env.PUBLIC_URL + "/img/panda.png", 'Panda', []);
|
||||
export default {rightArrow, leftArrow, downArrow, clear, panda};
|
||||
const panda = createImageComponent(
|
||||
process.env.PUBLIC_URL + "/img/panda.png",
|
||||
"Panda",
|
||||
[]
|
||||
);
|
||||
export default { rightArrow, leftArrow, downArrow, clear, panda };
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import {StyleSheet} from 'aphrodite/no-important';
|
||||
import { StyleSheet } from "aphrodite/no-important";
|
||||
|
||||
export default StyleSheet.create({
|
||||
tab: {
|
||||
display: 'inline',
|
||||
display: "inline",
|
||||
marginRight: 20,
|
||||
cursor: 'pointer'
|
||||
cursor: "pointer"
|
||||
},
|
||||
selectedTab: {
|
||||
borderBottom: '2px solid #000',
|
||||
fontWeight: 'bold'
|
||||
borderBottom: "2px solid #000",
|
||||
fontWeight: "bold"
|
||||
},
|
||||
tabContent: {
|
||||
paddingTop: 20
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import {StyleSheet} from 'aphrodite/no-important';
|
||||
import { StyleSheet } from "aphrodite/no-important";
|
||||
|
||||
export default StyleSheet.create({
|
||||
root: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
button: {
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'RGB(63, 135, 255)',
|
||||
borderRadius: 5,
|
||||
minWidth: 80,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
':hover': {
|
||||
backgroundColor: 'RGBA(63, 135, 255, 0.5)'
|
||||
}
|
||||
root: {
|
||||
flexDirection: "row"
|
||||
},
|
||||
button: {
|
||||
cursor: "pointer",
|
||||
backgroundColor: "RGB(63, 135, 255)",
|
||||
borderRadius: 5,
|
||||
minWidth: 80,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "white",
|
||||
":hover": {
|
||||
backgroundColor: "RGBA(63, 135, 255, 0.5)"
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
function elementWiseEquals(arr1, arr2) {
|
||||
return arr1.length === arr2.length && arr1.every((ele, idx) => arr2[idx] === ele);
|
||||
return (
|
||||
arr1.length === arr2.length && arr1.every((ele, idx) => arr2[idx] === ele)
|
||||
);
|
||||
}
|
||||
|
||||
function findIndexRight(arr, condition) {
|
||||
for(let i = arr.length - 1; i >= 0; i--) {
|
||||
if(condition(arr[i])) {
|
||||
return i;
|
||||
}
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
if (condition(arr[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {elementWiseEquals, findIndexRight};
|
||||
export default { elementWiseEquals, findIndexRight };
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
export function shade([r, g, b], opacity) {
|
||||
/*
|
||||
/*
|
||||
@param {Array} rgb
|
||||
@param {Number} opacity -1.0 -> +1.0
|
||||
negative opacity will darken image
|
||||
|
||||
returns new RGB array
|
||||
*/
|
||||
const t = (opacity < 0) ? 0 : 255;
|
||||
const opacityPos = Math.abs(opacity);
|
||||
const t = opacity < 0 ? 0 : 255;
|
||||
const opacityPos = Math.abs(opacity);
|
||||
|
||||
return [Math.round((t - r) * opacityPos) + r,
|
||||
Math.round((t - g) * opacityPos) + g,
|
||||
Math.round((t - b) * opacityPos) + b];
|
||||
}
|
||||
return [
|
||||
Math.round((t - r) * opacityPos) + r,
|
||||
Math.round((t - g) * opacityPos) + g,
|
||||
Math.round((t - b) * opacityPos) + b
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,19 +4,17 @@
|
|||
|
||||
// Useful for component testing
|
||||
|
||||
import { css } from 'aphrodite';
|
||||
import classNames from 'classnames';
|
||||
import { css } from "aphrodite";
|
||||
import classNames from "classnames";
|
||||
|
||||
export default function() {
|
||||
const styles = [];
|
||||
const styles = [];
|
||||
const classes = [];
|
||||
|
||||
[].forEach.call(arguments, it => {
|
||||
if (it && it._definition && it._name)
|
||||
styles.push(it);
|
||||
else
|
||||
classes.push(it);
|
||||
if (it && it._definition && it._name) styles.push(it);
|
||||
else classes.push(it);
|
||||
});
|
||||
|
||||
return classNames.apply(null, [ ...classes, css(styles) ]);
|
||||
return classNames.apply(null, [...classes, css(styles)]);
|
||||
}
|
||||
|
|
237
src/utils/dbc.js
237
src/utils/dbc.js
|
@ -1,145 +1,184 @@
|
|||
require('core-js/fn/array/from');
|
||||
require("core-js/fn/array/from");
|
||||
|
||||
function findMaxByteStateChangeCount(messages) {
|
||||
return Object.values(messages).map((m) => m.byteStateChangeCounts)
|
||||
.reduce((counts, countArr) => counts.concat(countArr), []) // flatten arrays
|
||||
.reduce((count1, count2) => count1 > count2 ? count1 : count2, 0); // find max
|
||||
return Object.values(messages)
|
||||
.map(m => m.byteStateChangeCounts)
|
||||
.reduce((counts, countArr) => counts.concat(countArr), []) // flatten arrays
|
||||
.reduce((count1, count2) => (count1 > count2 ? count1 : count2), 0); // find max
|
||||
}
|
||||
|
||||
function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
|
||||
var id = source + ":" + address.toString(16);
|
||||
function addCanMessage(
|
||||
[address, busTime, data, source],
|
||||
dbc,
|
||||
canStartTime,
|
||||
messages,
|
||||
prevMsgEntries,
|
||||
byteStateChangeCountsByMessage
|
||||
) {
|
||||
var id = source + ":" + address.toString(16);
|
||||
|
||||
if (messages[id] === undefined) messages[id] = createMessageSpec(dbc, address, id, source);
|
||||
if (messages[id] === undefined)
|
||||
messages[id] = createMessageSpec(dbc, address, id, source);
|
||||
|
||||
const prevMsgEntry = messages[id].entries.length > 0 ?
|
||||
messages[id].entries[messages[id].entries.length - 1]
|
||||
:
|
||||
(prevMsgEntries[id] || null);
|
||||
const prevMsgEntry =
|
||||
messages[id].entries.length > 0
|
||||
? messages[id].entries[messages[id].entries.length - 1]
|
||||
: prevMsgEntries[id] || null;
|
||||
|
||||
if(byteStateChangeCountsByMessage[id] && messages[id].byteStateChangeCounts.every((c) => c === 0)) {
|
||||
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id]
|
||||
}
|
||||
if (
|
||||
byteStateChangeCountsByMessage[id] &&
|
||||
messages[id].byteStateChangeCounts.every(c => c === 0)
|
||||
) {
|
||||
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id];
|
||||
}
|
||||
|
||||
const {msgEntry,
|
||||
byteStateChangeCounts} = parseMessage(dbc,
|
||||
busTime,
|
||||
address,
|
||||
data,
|
||||
canStartTime,
|
||||
prevMsgEntry);
|
||||
const { msgEntry, byteStateChangeCounts } = parseMessage(
|
||||
dbc,
|
||||
busTime,
|
||||
address,
|
||||
data,
|
||||
canStartTime,
|
||||
prevMsgEntry
|
||||
);
|
||||
|
||||
messages[id].byteStateChangeCounts = byteStateChangeCounts.map((count, idx) =>
|
||||
messages[id].byteStateChangeCounts[idx] + count
|
||||
);
|
||||
messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
|
||||
(count, idx) => messages[id].byteStateChangeCounts[idx] + count
|
||||
);
|
||||
|
||||
messages[id].entries.push(msgEntry);
|
||||
messages[id].entries.push(msgEntry);
|
||||
|
||||
return msgEntry;
|
||||
return msgEntry;
|
||||
}
|
||||
|
||||
function createMessageSpec(dbc, address, id, bus) {
|
||||
const frame = dbc.messages.get(address);
|
||||
const size = frame ? frame.size : 8;
|
||||
const frame = dbc.messages.get(address);
|
||||
const size = frame ? frame.size : 8;
|
||||
|
||||
return {
|
||||
address: address,
|
||||
id: id,
|
||||
bus: bus,
|
||||
entries: [],
|
||||
frame: frame,
|
||||
byteColors: Array(size).fill(0),
|
||||
byteStateChangeCounts: Array(size).fill(0)
|
||||
};
|
||||
return {
|
||||
address: address,
|
||||
id: id,
|
||||
bus: bus,
|
||||
entries: [],
|
||||
frame: frame,
|
||||
byteColors: Array(size).fill(0),
|
||||
byteStateChangeCounts: Array(size).fill(0)
|
||||
};
|
||||
}
|
||||
|
||||
function determineByteStateChangeTimes(hexData, time, msgSize, lastParsedMessage) {
|
||||
const byteStateChangeCounts = Array(msgSize).fill(0);
|
||||
let byteStateChangeTimes;
|
||||
function determineByteStateChangeTimes(
|
||||
hexData,
|
||||
time,
|
||||
msgSize,
|
||||
lastParsedMessage
|
||||
) {
|
||||
const byteStateChangeCounts = Array(msgSize).fill(0);
|
||||
let byteStateChangeTimes;
|
||||
|
||||
if(!lastParsedMessage) {
|
||||
byteStateChangeTimes = Array(msgSize).fill(time);
|
||||
} else {
|
||||
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
|
||||
if (!lastParsedMessage) {
|
||||
byteStateChangeTimes = Array(msgSize).fill(time);
|
||||
} else {
|
||||
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
|
||||
|
||||
for(let i = 0; i < byteStateChangeTimes.length; i++) {
|
||||
const currentData = hexData.substr(i * 2, 2),
|
||||
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
|
||||
for (let i = 0; i < byteStateChangeTimes.length; i++) {
|
||||
const currentData = hexData.substr(i * 2, 2),
|
||||
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
|
||||
|
||||
if(currentData !== prevData) {
|
||||
byteStateChangeTimes[i] = time;
|
||||
byteStateChangeCounts[i] = 1;
|
||||
}
|
||||
if (currentData !== prevData) {
|
||||
byteStateChangeTimes[i] = time;
|
||||
byteStateChangeCounts[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {byteStateChangeTimes, byteStateChangeCounts};
|
||||
return { byteStateChangeTimes, byteStateChangeCounts };
|
||||
}
|
||||
|
||||
function createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes) {
|
||||
return {
|
||||
signals: dbc.getSignalValues(address, data),
|
||||
time,
|
||||
relTime,
|
||||
hexData: Buffer.from(data).toString('hex'),
|
||||
byteStateChangeTimes,
|
||||
updated: Date.now(),
|
||||
};
|
||||
function createMessageEntry(
|
||||
dbc,
|
||||
address,
|
||||
time,
|
||||
relTime,
|
||||
data,
|
||||
byteStateChangeTimes
|
||||
) {
|
||||
return {
|
||||
signals: dbc.getSignalValues(address, data),
|
||||
time,
|
||||
relTime,
|
||||
hexData: Buffer.from(data).toString("hex"),
|
||||
byteStateChangeTimes,
|
||||
updated: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
function parseMessage(dbc, time, address, data, timeStart, lastParsedMessage) {
|
||||
let hexData;
|
||||
if(typeof data === 'string') {
|
||||
hexData = data;
|
||||
data = Buffer.from(data, 'hex');
|
||||
} else {
|
||||
hexData = Buffer.from(data).toString('hex');
|
||||
}
|
||||
const msgSpec = dbc.messages.get(address);
|
||||
const msgSize = msgSpec ? msgSpec.size : 8;
|
||||
const relTime = time - timeStart;
|
||||
let hexData;
|
||||
if (typeof data === "string") {
|
||||
hexData = data;
|
||||
data = Buffer.from(data, "hex");
|
||||
} else {
|
||||
hexData = Buffer.from(data).toString("hex");
|
||||
}
|
||||
const msgSpec = dbc.messages.get(address);
|
||||
const msgSize = msgSpec ? msgSpec.size : 8;
|
||||
const relTime = time - timeStart;
|
||||
|
||||
const {byteStateChangeTimes,
|
||||
byteStateChangeCounts} = determineByteStateChangeTimes(hexData,
|
||||
relTime,
|
||||
msgSize,
|
||||
lastParsedMessage);
|
||||
const msgEntry = createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes);
|
||||
const {
|
||||
byteStateChangeTimes,
|
||||
byteStateChangeCounts
|
||||
} = determineByteStateChangeTimes(
|
||||
hexData,
|
||||
relTime,
|
||||
msgSize,
|
||||
lastParsedMessage
|
||||
);
|
||||
const msgEntry = createMessageEntry(
|
||||
dbc,
|
||||
address,
|
||||
time,
|
||||
relTime,
|
||||
data,
|
||||
byteStateChangeTimes
|
||||
);
|
||||
|
||||
return {msgEntry, byteStateChangeCounts};
|
||||
return { msgEntry, byteStateChangeCounts };
|
||||
}
|
||||
|
||||
const BIG_ENDIAN_START_BITS = [];
|
||||
for(let i = 0; i < 64; i += 8) {
|
||||
for(let j = 7; j > -1; j--) {
|
||||
BIG_ENDIAN_START_BITS.push(i + j);
|
||||
}
|
||||
for (let i = 0; i < 64; i += 8) {
|
||||
for (let j = 7; j > -1; j--) {
|
||||
BIG_ENDIAN_START_BITS.push(i + j);
|
||||
}
|
||||
}
|
||||
|
||||
function bigEndianBitIndex(matrixBitIndex) {
|
||||
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
|
||||
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
|
||||
}
|
||||
|
||||
function matrixBitNumber(bigEndianIndex) {
|
||||
return BIG_ENDIAN_START_BITS[bigEndianIndex];
|
||||
return BIG_ENDIAN_START_BITS[bigEndianIndex];
|
||||
}
|
||||
|
||||
function setMessageByteColors(message, maxByteStateChangeCount) {
|
||||
message.byteColors = message.byteStateChangeCounts.map((count) =>
|
||||
isNaN(count) ? 0 : Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
|
||||
).map((red) =>
|
||||
'rgb(' + Math.round(red) + ',0,0)'
|
||||
);
|
||||
|
||||
message.byteColors = message.byteStateChangeCounts
|
||||
.map(
|
||||
count =>
|
||||
isNaN(count)
|
||||
? 0
|
||||
: Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
|
||||
)
|
||||
.map(red => "rgb(" + Math.round(red) + ",0,0)");
|
||||
|
||||
return message;
|
||||
|
||||
}
|
||||
|
||||
export default {bigEndianBitIndex,
|
||||
addCanMessage,
|
||||
createMessageSpec,
|
||||
matrixBitNumber,
|
||||
parseMessage,
|
||||
findMaxByteStateChangeCount,
|
||||
setMessageByteColors,
|
||||
createMessageEntry};
|
||||
export default {
|
||||
bigEndianBitIndex,
|
||||
addCanMessage,
|
||||
createMessageSpec,
|
||||
matrixBitNumber,
|
||||
parseMessage,
|
||||
findMaxByteStateChangeCount,
|
||||
setMessageByteColors,
|
||||
createMessageEntry
|
||||
};
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
export default function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this,
|
||||
args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,58 +1,67 @@
|
|||
const binTimeIntervals = [
|
||||
{seconds: 1,
|
||||
title: 'second'},
|
||||
{seconds: 60,
|
||||
title: 'minute'},
|
||||
{seconds: 300,
|
||||
title: '5 minutes'},
|
||||
{seconds: 3600,
|
||||
title: 'hour'}
|
||||
{
|
||||
seconds: 1,
|
||||
title: "second"
|
||||
},
|
||||
{
|
||||
seconds: 60,
|
||||
title: "minute"
|
||||
},
|
||||
{
|
||||
seconds: 300,
|
||||
title: "5 minutes"
|
||||
},
|
||||
{
|
||||
seconds: 3600,
|
||||
title: "hour"
|
||||
}
|
||||
];
|
||||
|
||||
function prettyBinDuration(samplesDurationSeconds, maxBinCount = 100) {
|
||||
for(let i = 0; i < binTimeIntervals.length; i++) {
|
||||
const interval = binTimeIntervals[i];
|
||||
if(samplesDurationSeconds / interval.seconds <= maxBinCount) {
|
||||
return interval;
|
||||
}
|
||||
for (let i = 0; i < binTimeIntervals.length; i++) {
|
||||
const interval = binTimeIntervals[i];
|
||||
if (samplesDurationSeconds / interval.seconds <= maxBinCount) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
|
||||
// maxBinCount is exceeded.
|
||||
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
|
||||
// Just going to use hours.
|
||||
// maxBinCount is exceeded.
|
||||
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
|
||||
// Just going to use hours.
|
||||
|
||||
return binTimeIntervals[3];
|
||||
return binTimeIntervals[3];
|
||||
}
|
||||
|
||||
export function binMessages(messageEntries, segmentIndices) {
|
||||
let startIdx = 0, endIdx = messageEntries.length - 1;
|
||||
if(segmentIndices && segmentIndices.length === 2) {
|
||||
[startIdx, endIdx] = segmentIndices;
|
||||
}
|
||||
const first = messageEntries[startIdx], last = messageEntries[endIdx];
|
||||
const binDuration = prettyBinDuration(last.time - first.time);
|
||||
let startIdx = 0,
|
||||
endIdx = messageEntries.length - 1;
|
||||
if (segmentIndices && segmentIndices.length === 2) {
|
||||
[startIdx, endIdx] = segmentIndices;
|
||||
}
|
||||
const first = messageEntries[startIdx],
|
||||
last = messageEntries[endIdx];
|
||||
const binDuration = prettyBinDuration(last.time - first.time);
|
||||
|
||||
const bins = [];
|
||||
const offset = first.time - first.relTime;
|
||||
const bins = [];
|
||||
const offset = first.time - first.relTime;
|
||||
|
||||
for(let t = first.time; t < last.time; t += binDuration.seconds) {
|
||||
const binCutoffTime = t + binDuration.seconds;
|
||||
const previousBinEnd = startIdx;
|
||||
for (let t = first.time; t < last.time; t += binDuration.seconds) {
|
||||
const binCutoffTime = t + binDuration.seconds;
|
||||
const previousBinEnd = startIdx;
|
||||
|
||||
let entry = {time: -1}
|
||||
while(entry !== undefined && entry.time < binCutoffTime) {
|
||||
entry = messageEntries[startIdx];
|
||||
++startIdx;
|
||||
}
|
||||
|
||||
|
||||
bins.push({
|
||||
count: startIdx - previousBinEnd,
|
||||
startTime: t,
|
||||
endTime: binCutoffTime,
|
||||
relStartTime: t - offset
|
||||
});
|
||||
let entry = { time: -1 };
|
||||
while (entry !== undefined && entry.time < binCutoffTime) {
|
||||
entry = messageEntries[startIdx];
|
||||
++startIdx;
|
||||
}
|
||||
|
||||
return {bins: bins, binDuration}
|
||||
bins.push({
|
||||
count: startIdx - previousBinEnd,
|
||||
startTime: t,
|
||||
endTime: binCutoffTime,
|
||||
relStartTime: t - offset
|
||||
});
|
||||
}
|
||||
|
||||
return { bins: bins, binDuration };
|
||||
}
|
||||
|
|
|
@ -2,130 +2,133 @@
|
|||
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
|
||||
|
||||
const NumpyLoader = (function NumpyLoader() {
|
||||
function asciiDecode(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
||||
function asciiDecode(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
||||
}
|
||||
|
||||
function readUint16LE(buffer) {
|
||||
var view = new DataView(buffer);
|
||||
var val = view.getUint8(0);
|
||||
val |= view.getUint8(1) << 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
function fromArrayBuffer(buf) {
|
||||
// Check the magic number
|
||||
var magic = asciiDecode(buf.slice(0, 6));
|
||||
if (magic !== "\x93NUMPY") {
|
||||
throw new Error("Bad magic number");
|
||||
}
|
||||
|
||||
function readUint16LE(buffer) {
|
||||
var view = new DataView(buffer);
|
||||
var val = view.getUint8(0);
|
||||
val |= view.getUint8(1) << 8;
|
||||
return val;
|
||||
}
|
||||
var version = new Uint8Array(buf.slice(6, 8)),
|
||||
headerLength = readUint16LE(buf.slice(8, 10)),
|
||||
headerStr = asciiDecode(buf.slice(10, 10 + headerLength));
|
||||
const offsetBytes = 10 + headerLength;
|
||||
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
|
||||
|
||||
function fromArrayBuffer(buf) {
|
||||
// Check the magic number
|
||||
var magic = asciiDecode(buf.slice(0,6));
|
||||
if (magic !== "\x93NUMPY") {
|
||||
throw new Error("Bad magic number");
|
||||
}
|
||||
// Hacky conversion of dict literal string to JS Object
|
||||
const info = JSON.parse(
|
||||
headerStr
|
||||
.toLowerCase()
|
||||
.replace("(", "[")
|
||||
.replace("),", "]")
|
||||
.replace(/'/g, '"')
|
||||
.replace(",]", "]")
|
||||
);
|
||||
|
||||
var version = new Uint8Array(buf.slice(6,8)),
|
||||
headerLength = readUint16LE(buf.slice(8,10)),
|
||||
headerStr = asciiDecode(buf.slice(10, 10+headerLength));
|
||||
const offsetBytes = 10 + headerLength;
|
||||
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
|
||||
|
||||
// Hacky conversion of dict literal string to JS Object
|
||||
const info = JSON.parse(headerStr.toLowerCase()
|
||||
.replace('(','[')
|
||||
.replace('),',']')
|
||||
.replace(/'/g, '"')
|
||||
.replace(',]', ']'));
|
||||
|
||||
// Intepret the bytes according to the specified dtype
|
||||
var data;
|
||||
if (info.descr === "|u1") {
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|i1") {
|
||||
data = new Int8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u2") {
|
||||
data = new Uint16Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i2") {
|
||||
data = new Int16Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u4") {
|
||||
data = new Uint32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i4") {
|
||||
data = new Int32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<f4") {
|
||||
data = new Float32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<f8") {
|
||||
data = new Float64Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u8") {
|
||||
// 8 byte uint64s
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i8") {
|
||||
// 8 byte int64s
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|s5") {
|
||||
// 5 byte string
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|s8") {
|
||||
// 8 byte strings
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === '|s15') {
|
||||
// 15 byte strings?
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else {
|
||||
throw new Error('unknown numeric dtype '+info.descr)
|
||||
}
|
||||
|
||||
return {
|
||||
shape: info.shape,
|
||||
fortran_order: info.fortran_order,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
|
||||
function open(file, callback) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// the file contents have been read as an array buffer
|
||||
var buf = reader.result;
|
||||
var ndarray = fromArrayBuffer(buf);
|
||||
callback(ndarray);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function promise(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Do the usual XHR stuff
|
||||
var req = new XMLHttpRequest();
|
||||
req.onload = function() {
|
||||
// This is called even on 404 etc
|
||||
// so check the status
|
||||
if (req.status == 200) {
|
||||
var buf = req.response; // not responseText
|
||||
var ndarray = fromArrayBuffer(buf);
|
||||
resolve(ndarray);
|
||||
} else if (req.status == 404) {
|
||||
console.log('yup')
|
||||
reject({is404: true})
|
||||
} else {
|
||||
// Otherwise reject with the status text
|
||||
// which will hopefully be a meaningful error
|
||||
reject(Error(req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network errors
|
||||
req.onerror = function() {
|
||||
reject(Error("Network Error"));
|
||||
};
|
||||
|
||||
// Make the request
|
||||
req.open('GET', url, true);
|
||||
req.responseType = "arraybuffer";
|
||||
req.send(null);
|
||||
});
|
||||
// Intepret the bytes according to the specified dtype
|
||||
var data;
|
||||
if (info.descr === "|u1") {
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|i1") {
|
||||
data = new Int8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u2") {
|
||||
data = new Uint16Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i2") {
|
||||
data = new Int16Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u4") {
|
||||
data = new Uint32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i4") {
|
||||
data = new Int32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<f4") {
|
||||
data = new Float32Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<f8") {
|
||||
data = new Float64Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<u8") {
|
||||
// 8 byte uint64s
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "<i8") {
|
||||
// 8 byte int64s
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|s5") {
|
||||
// 5 byte string
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|s8") {
|
||||
// 8 byte strings
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else if (info.descr === "|s15") {
|
||||
// 15 byte strings?
|
||||
data = new Uint8Array(buf, offsetBytes);
|
||||
} else {
|
||||
throw new Error("unknown numeric dtype " + info.descr);
|
||||
}
|
||||
|
||||
return {
|
||||
open: open,
|
||||
promise: promise,
|
||||
fromArrayBuffer: fromArrayBuffer,
|
||||
shape: info.shape,
|
||||
fortran_order: info.fortran_order,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
|
||||
function open(file, callback) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// the file contents have been read as an array buffer
|
||||
var buf = reader.result;
|
||||
var ndarray = fromArrayBuffer(buf);
|
||||
callback(ndarray);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function promise(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Do the usual XHR stuff
|
||||
var req = new XMLHttpRequest();
|
||||
req.onload = function() {
|
||||
// This is called even on 404 etc
|
||||
// so check the status
|
||||
if (req.status == 200) {
|
||||
var buf = req.response; // not responseText
|
||||
var ndarray = fromArrayBuffer(buf);
|
||||
resolve(ndarray);
|
||||
} else if (req.status == 404) {
|
||||
console.log("yup");
|
||||
reject({ is404: true });
|
||||
} else {
|
||||
// Otherwise reject with the status text
|
||||
// which will hopefully be a meaningful error
|
||||
reject(Error(req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network errors
|
||||
req.onerror = function() {
|
||||
reject(Error("Network Error"));
|
||||
};
|
||||
|
||||
// Make the request
|
||||
req.open("GET", url, true);
|
||||
req.responseType = "arraybuffer";
|
||||
req.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
open: open,
|
||||
promise: promise,
|
||||
fromArrayBuffer: fromArrayBuffer
|
||||
};
|
||||
})();
|
||||
|
||||
module.exports = NumpyLoader;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
export function swapKeysAndValues(obj, f) {
|
||||
return Object.keys(obj).reduce(function(acc, k) {
|
||||
acc[obj[k]] = k;
|
||||
return acc
|
||||
},{})
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function fromArray(arr) {
|
||||
// arr is an array of array key-value pairs
|
||||
// like [['a', 1], ['b', 2]]
|
||||
// arr is an array of array key-value pairs
|
||||
// like [['a', 1], ['b', 2]]
|
||||
|
||||
const pairs = arr.map( ([k,v]) => ({[k]: v}));
|
||||
if(pairs.length > 0) {
|
||||
return Object.assign(...pairs);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
const pairs = arr.map(([k, v]) => ({ [k]: v }));
|
||||
if (pairs.length > 0) {
|
||||
return Object.assign(...pairs);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
export function hash(str) {
|
||||
var hash = 5381,
|
||||
i = str.length;
|
||||
i = str.length;
|
||||
|
||||
while(i) {
|
||||
while (i) {
|
||||
hash = (hash * 33) ^ str.charCodeAt(--i);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,173 +1,184 @@
|
|||
import {createClassFromSpec} from 'react-vega';
|
||||
import { createClassFromSpec } from "react-vega";
|
||||
|
||||
const canHistogramSpec = {
|
||||
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
|
||||
"width": 1000,
|
||||
"height": 100,
|
||||
"padding": 5,
|
||||
$schema: "https://vega.github.io/schema/vega/v3.0.json",
|
||||
width: 1000,
|
||||
height: 100,
|
||||
padding: 5,
|
||||
|
||||
"signals": [
|
||||
signals: [
|
||||
{
|
||||
"name": "segment"
|
||||
name: "segment"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
data: [
|
||||
{
|
||||
"name": "binned"
|
||||
name: "binned"
|
||||
}
|
||||
],
|
||||
|
||||
"scales": [
|
||||
scales: [
|
||||
{
|
||||
"name": "xscale",
|
||||
"type": "linear",
|
||||
"zero": false,
|
||||
"range": "width",
|
||||
"domain": {"data": "binned", "field": "startTime"}
|
||||
name: "xscale",
|
||||
type: "linear",
|
||||
zero: false,
|
||||
range: "width",
|
||||
domain: { data: "binned", field: "startTime" }
|
||||
},
|
||||
{
|
||||
"name": "xrelscale",
|
||||
"type": "linear",
|
||||
"zero": false,
|
||||
"range": "width",
|
||||
"domain": {"data": "binned", "field": "relStartTime"}
|
||||
name: "xrelscale",
|
||||
type: "linear",
|
||||
zero: false,
|
||||
range: "width",
|
||||
domain: { data: "binned", field: "relStartTime" }
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"type": "linear",
|
||||
"range": "height", "round": true,
|
||||
"domain": {"data": "binned", "field": "count"},
|
||||
"zero": true, "nice": true
|
||||
name: "yscale",
|
||||
type: "linear",
|
||||
range: "height",
|
||||
round: true,
|
||||
domain: { data: "binned", field: "count" },
|
||||
zero: true,
|
||||
nice: true
|
||||
}
|
||||
],
|
||||
|
||||
"axes": [
|
||||
{"orient": "bottom", "scale": "xrelscale", "zindex": 1, "tickCount": 10},
|
||||
{"orient": "left", "scale": "yscale", "tickCount": 5, "zindex": 1}
|
||||
axes: [
|
||||
{ orient: "bottom", scale: "xrelscale", zindex: 1, tickCount: 10 },
|
||||
{ orient: "left", scale: "yscale", tickCount: 5, zindex: 1 }
|
||||
],
|
||||
|
||||
"marks": [
|
||||
{"type": "group",
|
||||
"name": "histogram",
|
||||
"interactive": true,
|
||||
"encode": {
|
||||
"enter": {
|
||||
"height": {"value": 75},
|
||||
"width": {"value": 1000},
|
||||
"fill": {"value": "transparent"}
|
||||
}
|
||||
},
|
||||
"signals": [
|
||||
marks: [
|
||||
{
|
||||
type: "group",
|
||||
name: "histogram",
|
||||
interactive: true,
|
||||
encode: {
|
||||
enter: {
|
||||
height: { value: 75 },
|
||||
width: { value: 1000 },
|
||||
fill: { value: "transparent" }
|
||||
}
|
||||
},
|
||||
signals: [
|
||||
{
|
||||
"name": "brush", "value": 0,
|
||||
"on": [
|
||||
name: "brush",
|
||||
value: 0,
|
||||
on: [
|
||||
{
|
||||
"events": "@bins:mousedown",
|
||||
"update": "[x(), x()]"
|
||||
events: "@bins:mousedown",
|
||||
update: "[x(), x()]"
|
||||
},
|
||||
{
|
||||
"events": "[@bins:mousedown, window:mouseup] > window:mousemove!",
|
||||
"update": "[brush[0], clamp(x(), 0, width)]"
|
||||
events: "[@bins:mousedown, window:mouseup] > window:mousemove!",
|
||||
update: "[brush[0], clamp(x(), 0, width)]"
|
||||
},
|
||||
{
|
||||
"events": {"signal": "delta"},
|
||||
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||
events: { signal: "delta" },
|
||||
update:
|
||||
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "anchor", "value": null,
|
||||
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
|
||||
name: "anchor",
|
||||
value: null,
|
||||
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
|
||||
},
|
||||
{
|
||||
"name": "xdown", "value": 0,
|
||||
"on": [{"events": "@brush:mousedown", "update": "x()"}]
|
||||
name: "xdown",
|
||||
value: 0,
|
||||
on: [{ events: "@brush:mousedown", update: "x()" }]
|
||||
},
|
||||
{
|
||||
"name": "delta", "value": 0,
|
||||
"on": [
|
||||
name: "delta",
|
||||
value: 0,
|
||||
on: [
|
||||
{
|
||||
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||
"update": "x() - xdown"
|
||||
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||
update: "x() - xdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "segment",
|
||||
"push": "outer",
|
||||
"on": [
|
||||
name: "segment",
|
||||
push: "outer",
|
||||
on: [
|
||||
{
|
||||
"events": {"signal": "brush"},
|
||||
"update": "span(brush) ? invert('xscale', brush) : null"
|
||||
events: { signal: "brush" },
|
||||
update: "span(brush) ? invert('xscale', brush) : null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"marks": [
|
||||
{
|
||||
"type": "rect",
|
||||
"interactive": true,
|
||||
"from": {"data": "binned"},
|
||||
"name": "bins",
|
||||
"encode": {
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "startTime"},
|
||||
"x2": {"scale": "xscale", "field": "endTime",
|
||||
"offset": 0},
|
||||
"y": {"scale": "yscale", "field": "count"},
|
||||
"y2": {"scale": "yscale", "value": 0},
|
||||
"fill": {"value": "steelblue"}
|
||||
],
|
||||
marks: [
|
||||
{
|
||||
type: "rect",
|
||||
interactive: true,
|
||||
from: { data: "binned" },
|
||||
name: "bins",
|
||||
encode: {
|
||||
update: {
|
||||
x: { scale: "xscale", field: "startTime" },
|
||||
x2: {
|
||||
scale: "xscale",
|
||||
field: "endTime",
|
||||
offset: 0
|
||||
},
|
||||
y: { scale: "yscale", field: "count" },
|
||||
y2: { scale: "yscale", value: 0 },
|
||||
fill: { value: "steelblue" }
|
||||
},
|
||||
"hover": {
|
||||
"fill": {"value": "goldenrod"},
|
||||
"cursor": {"value": "ew-resize"}
|
||||
hover: {
|
||||
fill: { value: "goldenrod" },
|
||||
cursor: { value: "ew-resize" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"interactive": true,
|
||||
"name": "brush",
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y": {"value": 0},
|
||||
"height": {"value": 100},
|
||||
"fill": {"value": "#333"},
|
||||
"fillOpacity": {"value": 0.2}
|
||||
type: "rect",
|
||||
interactive: true,
|
||||
name: "brush",
|
||||
encode: {
|
||||
enter: {
|
||||
y: { value: 0 },
|
||||
height: { value: 100 },
|
||||
fill: { value: "#333" },
|
||||
fillOpacity: { value: 0.2 }
|
||||
},
|
||||
"update": {
|
||||
"x": {"signal": "brush[0]"},
|
||||
"x2": {"signal": "brush[1]"}
|
||||
update: {
|
||||
x: { signal: "brush[0]" },
|
||||
x2: { signal: "brush[1]" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"interactive": false,
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y": {"value": 0},
|
||||
"height": {"value": 100},
|
||||
"width": {"value": 2},
|
||||
"fill": {"value": "firebrick"}
|
||||
type: "rect",
|
||||
interactive: false,
|
||||
encode: {
|
||||
enter: {
|
||||
y: { value: 0 },
|
||||
height: { value: 100 },
|
||||
width: { value: 2 },
|
||||
fill: { value: "firebrick" }
|
||||
},
|
||||
"update": {
|
||||
"x": {"signal": "brush[0]"}
|
||||
update: {
|
||||
x: { signal: "brush[0]" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"interactive": false,
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y": {"value": 0},
|
||||
"height": {"value": 100},
|
||||
"width": {"value": 2},
|
||||
"fill": {"value": "firebrick"}
|
||||
type: "rect",
|
||||
interactive: false,
|
||||
encode: {
|
||||
enter: {
|
||||
y: { value: 0 },
|
||||
height: { value: 100 },
|
||||
width: { value: 2 },
|
||||
fill: { value: "firebrick" }
|
||||
},
|
||||
"update": {
|
||||
"x": {"signal": "brush[1]"}
|
||||
update: {
|
||||
x: { signal: "brush[1]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,306 +1,334 @@
|
|||
import {createClassFromSpec} from 'react-vega';
|
||||
import { createClassFromSpec } from "react-vega";
|
||||
|
||||
export default createClassFromSpec('CanPlot', {
|
||||
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"padding": "auto",
|
||||
"autosize": {"type": "fit", "resize": true},
|
||||
"signals": [
|
||||
export default createClassFromSpec("CanPlot", {
|
||||
$schema: "https://vega.github.io/schema/vega/v3.0.json",
|
||||
width: 500,
|
||||
height: 200,
|
||||
padding: "auto",
|
||||
autosize: { type: "fit", resize: true },
|
||||
signals: [
|
||||
{
|
||||
"name": "tipTime",
|
||||
"on": [{
|
||||
"events": "mousemove",
|
||||
"update": "invert('xrelscale', x())"
|
||||
}]
|
||||
},
|
||||
{"name": "clickTime",
|
||||
"on": [{
|
||||
"events": "mouseup",
|
||||
"update": "invert('xrelscale', x())"
|
||||
}]
|
||||
},
|
||||
{"name": "videoTime"},
|
||||
{"name": "segment",
|
||||
"value": {"data": "table", "field": "relTime"}
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"name": "table"
|
||||
},
|
||||
{
|
||||
"name": "tooltip",
|
||||
"source": "table",
|
||||
"transform": [
|
||||
name: "tipTime",
|
||||
on: [
|
||||
{
|
||||
"type": "filter",
|
||||
"expr": "abs(datum.relTime - tipTime) <= 0.01"
|
||||
},
|
||||
{
|
||||
"type": "aggregate",
|
||||
"fields": ["relTime", "y", "unit"],
|
||||
"ops": ["min", "argmin", "argmin"],
|
||||
"as": ["min", "argmin", "argmin"]
|
||||
events: "mousemove",
|
||||
update: "invert('xrelscale', x())"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ySegmentScale",
|
||||
"source": "table",
|
||||
"transform": [
|
||||
name: "clickTime",
|
||||
on: [
|
||||
{
|
||||
"type": "filter",
|
||||
"expr": "length(segment) != 2 || (datum.relTime >= segment[0] && datum.relTime <= segment[1])"
|
||||
events: "mouseup",
|
||||
update: "invert('xrelscale', x())"
|
||||
}
|
||||
]
|
||||
},
|
||||
{ name: "videoTime" },
|
||||
{
|
||||
name: "segment",
|
||||
value: { data: "table", field: "relTime" }
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
name: "table"
|
||||
},
|
||||
{
|
||||
name: "tooltip",
|
||||
source: "table",
|
||||
transform: [
|
||||
{
|
||||
type: "filter",
|
||||
expr: "abs(datum.relTime - tipTime) <= 0.01"
|
||||
},
|
||||
{"type": "extent", "field": "y", "signal": "ySegment"}
|
||||
{
|
||||
type: "aggregate",
|
||||
fields: ["relTime", "y", "unit"],
|
||||
ops: ["min", "argmin", "argmin"],
|
||||
as: ["min", "argmin", "argmin"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "ySegmentScale",
|
||||
source: "table",
|
||||
transform: [
|
||||
{
|
||||
type: "filter",
|
||||
expr:
|
||||
"length(segment) != 2 || (datum.relTime >= segment[0] && datum.relTime <= segment[1])"
|
||||
},
|
||||
{ type: "extent", field: "y", signal: "ySegment" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"scales": [
|
||||
scales: [
|
||||
{
|
||||
"name": "xscale",
|
||||
"type": "linear",
|
||||
"range": "width",
|
||||
"domain": {"data": "table", "field": "x"},
|
||||
"zero": false
|
||||
name: "xscale",
|
||||
type: "linear",
|
||||
range: "width",
|
||||
domain: { data: "table", field: "x" },
|
||||
zero: false
|
||||
},
|
||||
{
|
||||
"name": "xrelscale",
|
||||
"type": "linear",
|
||||
"range": "width",
|
||||
"domain": {"data": "table", "field": "relTime"},
|
||||
"zero": false,
|
||||
"clamp": true,
|
||||
"domainRaw": {"signal": "segment"}
|
||||
name: "xrelscale",
|
||||
type: "linear",
|
||||
range: "width",
|
||||
domain: { data: "table", field: "relTime" },
|
||||
zero: false,
|
||||
clamp: true,
|
||||
domainRaw: { signal: "segment" }
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"type": "linear",
|
||||
"range": "height",
|
||||
"clamp": true,
|
||||
"zero": false,
|
||||
"domain": {"signal": "ySegment"}
|
||||
name: "yscale",
|
||||
type: "linear",
|
||||
range: "height",
|
||||
clamp: true,
|
||||
zero: false,
|
||||
domain: { signal: "ySegment" }
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "ordinal",
|
||||
"domain": {"data": "table", "field": "color"},
|
||||
"range": {"data": "table", "field": "color"}
|
||||
name: "color",
|
||||
type: "ordinal",
|
||||
domain: { data: "table", field: "color" },
|
||||
range: { data: "table", field: "color" }
|
||||
}
|
||||
],
|
||||
"axes": [
|
||||
{"orient": "bottom", "scale": "xrelscale", labelOverlap: true},
|
||||
{"orient": "left", "scale": "yscale"}
|
||||
axes: [
|
||||
{ orient: "bottom", scale: "xrelscale", labelOverlap: true },
|
||||
{ orient: "left", scale: "yscale" }
|
||||
],
|
||||
"marks": [
|
||||
{"type": "group",
|
||||
"name": "plot",
|
||||
"interactive": true,
|
||||
"encode": {
|
||||
"enter": {
|
||||
"width": {"signal": "width"},
|
||||
"height": {"signal": "height"},
|
||||
"fill": {"value": "transparent"}
|
||||
}
|
||||
},
|
||||
"signals": [
|
||||
marks: [
|
||||
{
|
||||
type: "group",
|
||||
name: "plot",
|
||||
interactive: true,
|
||||
encode: {
|
||||
enter: {
|
||||
width: { signal: "width" },
|
||||
height: { signal: "height" },
|
||||
fill: { value: "transparent" }
|
||||
}
|
||||
},
|
||||
signals: [
|
||||
{
|
||||
"name": "brush", "value": 0,
|
||||
"on": [
|
||||
name: "brush",
|
||||
value: 0,
|
||||
on: [
|
||||
{
|
||||
"events": "@boundingRect:mousedown",
|
||||
"update": "[x(), x()]"
|
||||
events: "@boundingRect:mousedown",
|
||||
update: "[x(), x()]"
|
||||
},
|
||||
{
|
||||
"events": "[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
|
||||
"update": "[brush[0], clamp(x(), 0, width)]"
|
||||
events:
|
||||
"[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
|
||||
update: "[brush[0], clamp(x(), 0, width)]"
|
||||
},
|
||||
{
|
||||
"events": {"signal": "delta"},
|
||||
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||
events: { signal: "delta" },
|
||||
update:
|
||||
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "anchor", "value": null,
|
||||
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
|
||||
name: "anchor",
|
||||
value: null,
|
||||
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
|
||||
},
|
||||
{
|
||||
"name": "xdown", "value": 0,
|
||||
"on": [{"events": "@brush:mousedown", "update": "x()"}]
|
||||
name: "xdown",
|
||||
value: 0,
|
||||
on: [{ events: "@brush:mousedown", update: "x()" }]
|
||||
},
|
||||
{
|
||||
"name": "delta", "value": 0,
|
||||
"on": [
|
||||
name: "delta",
|
||||
value: 0,
|
||||
on: [
|
||||
{
|
||||
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||
"update": "x() - xdown"
|
||||
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||
update: "x() - xdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "segment",
|
||||
"push": "outer",
|
||||
"on": [
|
||||
name: "segment",
|
||||
push: "outer",
|
||||
on: [
|
||||
{
|
||||
"events": "window:mouseup",
|
||||
"update": "span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
|
||||
events: "window:mouseup",
|
||||
update:
|
||||
"span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"marks": [
|
||||
{
|
||||
"type": "group",
|
||||
"from": {
|
||||
"facet": {
|
||||
"name": "series",
|
||||
"data": "table",
|
||||
"groupby": "color"
|
||||
],
|
||||
marks: [
|
||||
{
|
||||
type: "group",
|
||||
from: {
|
||||
facet: {
|
||||
name: "series",
|
||||
data: "table",
|
||||
groupby: "color"
|
||||
}
|
||||
},
|
||||
marks: {
|
||||
type: "line",
|
||||
name: "lineMark",
|
||||
from: { data: "series" },
|
||||
interactive: true,
|
||||
encode: {
|
||||
update: {
|
||||
interpolate: { value: "step" },
|
||||
x: { scale: "xrelscale", field: "relTime" },
|
||||
y: { scale: "yscale", field: "y" }
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: { value: 0.5 }
|
||||
},
|
||||
enter: {
|
||||
clip: { value: true },
|
||||
stroke: { scale: "color", field: "color" },
|
||||
strokeWidth: { value: 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "rect",
|
||||
interactive: true,
|
||||
name: "brush",
|
||||
encode: {
|
||||
enter: {
|
||||
y: { value: 0 },
|
||||
height: { signal: "height" },
|
||||
fill: { value: "#333" },
|
||||
fillOpacity: { value: 0.2 }
|
||||
},
|
||||
"marks": {
|
||||
"type": "line",
|
||||
"name": "lineMark",
|
||||
"from": {"data": "series"},
|
||||
"interactive": true,
|
||||
"encode": {
|
||||
"update": {
|
||||
"interpolate": {"value": "step"},
|
||||
"x": {"scale": "xrelscale", "field": "relTime"},
|
||||
"y": {"scale": "yscale", "field": "y"}
|
||||
update: {
|
||||
x: { signal: "brush[0]" },
|
||||
x2: { signal: "brush[1]" }
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// "type": "rect",
|
||||
// "interactive": false,
|
||||
// "encode": {
|
||||
// "enter": {
|
||||
// "y": {"value": 0},
|
||||
// "height": {"value": 200},
|
||||
// "width": {"value": 2},
|
||||
// "fill": {"value": "firebrick"}
|
||||
// },
|
||||
// "update": {
|
||||
// "x": {"signal": "brush[0]"}
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "type": "rect",
|
||||
// "interactive": false,
|
||||
// "encode": {
|
||||
// "enter": {
|
||||
// "y": {"value": 0},
|
||||
// "height": {"value": 200},
|
||||
// "width": {"value": 2},
|
||||
// "fill": {"value": "firebrick"}
|
||||
// },
|
||||
// "update": {
|
||||
// "x": {"signal": "brush[1]"}
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
type: "rule",
|
||||
encode: {
|
||||
update: {
|
||||
y: { value: 0 },
|
||||
y2: { field: { group: "height" } },
|
||||
stroke: { value: "#000" },
|
||||
strokeWidth: { value: 2 },
|
||||
x: {
|
||||
scale: "xrelscale",
|
||||
signal: "videoTime",
|
||||
offset: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "symbol",
|
||||
from: { data: "tooltip" },
|
||||
encode: {
|
||||
update: {
|
||||
x: { scale: "xrelscale", field: "argmin.relTime" },
|
||||
y: { scale: "yscale", field: "argmin.y" },
|
||||
size: { value: 50 },
|
||||
fill: { value: "black" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "group",
|
||||
from: { data: "tooltip" },
|
||||
interactive: false,
|
||||
name: "tooltipGroup",
|
||||
encode: {
|
||||
update: {
|
||||
x: [
|
||||
{
|
||||
test:
|
||||
"inrange(datum.argmin.relTime + 80, domain('xrelscale'))",
|
||||
scale: "xrelscale",
|
||||
field: "argmin.relTime"
|
||||
},
|
||||
"hover": {
|
||||
"fillOpacity": {"value": 0.5}
|
||||
},
|
||||
"enter": {
|
||||
"clip": {"value": true},
|
||||
"stroke": {"scale": "color", "field": "color"},
|
||||
"strokeWidth": {"value": 2}
|
||||
{ scale: "xrelscale", field: "argmin.relTime", offset: -80 }
|
||||
],
|
||||
y: { scale: "yscale", field: "argmin.y" },
|
||||
height: { value: 20 },
|
||||
width: { value: 80 },
|
||||
fill: { value: "#fff" },
|
||||
fillOpacity: { value: 0.85 },
|
||||
stroke: { value: "#aaa" },
|
||||
strokeWidth: { value: 0.5 }
|
||||
}
|
||||
},
|
||||
marks: [
|
||||
{
|
||||
type: "text",
|
||||
interactive: false,
|
||||
encode: {
|
||||
update: {
|
||||
text: {
|
||||
signal:
|
||||
"format(parent.argmin.relTime, ',.2f') + ': ' + format(parent.argmin.y, ',.2f') + ' ' + parent.argmin.unit"
|
||||
},
|
||||
fill: { value: "black" },
|
||||
fontWeight: { value: "bold" },
|
||||
y: { value: 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"interactive": true,
|
||||
"name": "brush",
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y": {"value": 0},
|
||||
"height": {"signal": "height"},
|
||||
"fill": {"value": "#333"},
|
||||
"fillOpacity": {"value": 0.2}
|
||||
},
|
||||
"update": {
|
||||
"x": {"signal": "brush[0]"},
|
||||
"x2": {"signal": "brush[1]"}
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// "type": "rect",
|
||||
// "interactive": false,
|
||||
// "encode": {
|
||||
// "enter": {
|
||||
// "y": {"value": 0},
|
||||
// "height": {"value": 200},
|
||||
// "width": {"value": 2},
|
||||
// "fill": {"value": "firebrick"}
|
||||
// },
|
||||
// "update": {
|
||||
// "x": {"signal": "brush[0]"}
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "type": "rect",
|
||||
// "interactive": false,
|
||||
// "encode": {
|
||||
// "enter": {
|
||||
// "y": {"value": 0},
|
||||
// "height": {"value": 200},
|
||||
// "width": {"value": 2},
|
||||
// "fill": {"value": "firebrick"}
|
||||
// },
|
||||
// "update": {
|
||||
// "x": {"signal": "brush[1]"}
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
"type": "rule",
|
||||
"encode": {
|
||||
"update": {
|
||||
"y": {"value": 0},
|
||||
"y2": {"field": {"group": "height"}},
|
||||
"stroke": {"value": "#000"},
|
||||
"strokeWidth": {"value": 2},
|
||||
"x": {"scale": "xrelscale",
|
||||
"signal": "videoTime", "offset": 0.5}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "symbol",
|
||||
"from": {"data": "tooltip"},
|
||||
"encode": {
|
||||
"update": {
|
||||
"x": {"scale": "xrelscale", "field": "argmin.relTime"},
|
||||
"y": {"scale": "yscale", "field": "argmin.y"},
|
||||
"size": {"value": 50},
|
||||
"fill": {"value": "black"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "group",
|
||||
"from": {"data": "tooltip"},
|
||||
"interactive": false,
|
||||
"name": "tooltipGroup",
|
||||
"encode": {
|
||||
"update": {
|
||||
"x": [{"test": "inrange(datum.argmin.relTime + 80, domain('xrelscale'))", "scale": "xrelscale", "field": "argmin.relTime"},
|
||||
{"scale": "xrelscale", "field": "argmin.relTime", "offset": -80}],
|
||||
"y": {"scale": "yscale", "field": "argmin.y"},
|
||||
"height": {"value": 20},
|
||||
"width": {"value": 80},
|
||||
"fill": {"value": "#fff"},
|
||||
"fillOpacity": {"value": 0.85},
|
||||
"stroke": {"value": "#aaa"},
|
||||
"strokeWidth": {"value": 0.5}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"marks": [
|
||||
{
|
||||
"type": "text",
|
||||
"interactive": false,
|
||||
"encode": {
|
||||
"update": {
|
||||
"text": {"signal": "format(parent.argmin.relTime, ',.2f') + ': ' + format(parent.argmin.y, ',.2f') + ' ' + parent.argmin.unit"},
|
||||
"fill": {"value": "black"},
|
||||
"fontWeight": {"value": "bold"},
|
||||
"y": {"value": 20}
|
||||
}
|
||||
{
|
||||
type: "rect",
|
||||
name: "boundingRect",
|
||||
interactive: true,
|
||||
encode: {
|
||||
enter: {
|
||||
width: { signal: "width" },
|
||||
height: { signal: "height" },
|
||||
fill: { value: "transparent" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"name": "boundingRect",
|
||||
"interactive": true,
|
||||
"encode": {
|
||||
"enter": {
|
||||
"width": {"signal": "width"},
|
||||
"height": {"signal": "height"},
|
||||
"fill": {"value": "transparent"}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue