prettier the js
parent
40e7efad4c
commit
beb0a7ea01
12
package.json
12
package.json
|
@ -17,10 +17,13 @@
|
||||||
"github-api": "^3.0.0",
|
"github-api": "^3.0.0",
|
||||||
"hls": "0.0.1",
|
"hls": "0.0.1",
|
||||||
"hls.js": "^0.7.9",
|
"hls.js": "^0.7.9",
|
||||||
|
"husky": "^0.14.3",
|
||||||
"int64-buffer": "^0.1.9",
|
"int64-buffer": "^0.1.9",
|
||||||
"js-cookie": "^2.1.4",
|
"js-cookie": "^2.1.4",
|
||||||
"left-pad": "^1.1.3",
|
"left-pad": "^1.1.3",
|
||||||
|
"lint-staged": "^6.0.0",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
|
"prettier": "^1.9.2",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"raven-js": "^3.16.0",
|
"raven-js": "^3.16.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
|
@ -57,6 +60,13 @@
|
||||||
"build": "react-app-rewired build",
|
"build": "react-app-rewired build",
|
||||||
"test": "react-app-rewired test --env=jsdom",
|
"test": "react-app-rewired test --env=jsdom",
|
||||||
"sass":
|
"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
|
global.__JEST__ = 1;
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
import Signal from '../../models/can/signal';
|
import Signal from "../../models/can/signal";
|
||||||
|
|
||||||
test('setting signals should create a message', () => {
|
test("setting signals should create a message", () => {
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal'})});
|
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', () => {
|
test("setting signals should update DBC.boardUnits", () => {
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal', receiver: ['NEO']})});
|
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', () => {
|
test("adding a signal should update DBC.boardUnits", () => {
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
dbc.createFrame(100);
|
dbc.createFrame(100);
|
||||||
dbc.addSignal(100, new Signal({name: 'My Signal', receiver: ['NEO']}));
|
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
|
global.__JEST__ = 1;
|
||||||
import DBC, {swapOrder} from '../../models/can/dbc';
|
import DBC, { swapOrder } from "../../models/can/dbc";
|
||||||
import Signal from '../../models/can/signal';
|
import Signal from "../../models/can/signal";
|
||||||
import Bitarray from '../../models/bitarray';
|
import Bitarray from "../../models/bitarray";
|
||||||
|
|
||||||
const DBC_MESSAGE_DEF = `BO_ 228 STEERING_CONTROL: 5 ADAS
|
const DBC_MESSAGE_DEF = `BO_ 228 STEERING_CONTROL: 5 ADAS
|
||||||
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
|
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";`;
|
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
|
BO_ 228 STEERING_CONTROL: 5 ADAS
|
||||||
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
|
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
|
||||||
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" 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({
|
const steerTorqueSignal = new Signal({
|
||||||
name: 'STEER_TORQUE',
|
name: "STEER_TORQUE",
|
||||||
startBit: 7,
|
startBit: 7,
|
||||||
size: 16,
|
size: 16,
|
||||||
isLittleEndian: false,
|
isLittleEndian: false,
|
||||||
isSigned: true,
|
isSigned: true,
|
||||||
factor: 1,
|
factor: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
min: -3840,
|
min: -3840,
|
||||||
max: 3840,
|
max: 3840,
|
||||||
receiver: ['EPS'],
|
receiver: ["EPS"],
|
||||||
unit: ""});
|
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DBC parses signal comment', () => {
|
test("DBC parses steering control message", () => {
|
||||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
|
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
|
||||||
const {signals} = dbcParsed.messages.get(228);
|
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', () => {
|
test("DBC parses signal comment", () => {
|
||||||
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
|
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
|
||||||
const {signals} = dbcParsed.messages.get(228);
|
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', () => {
|
test("DBC parses multi-line signal comment", () => {
|
||||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
|
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
|
||||||
const msg = dbcParsed.messages.get(228);
|
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', () => {
|
test("DBC parses message comment", () => {
|
||||||
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
|
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
|
||||||
const msg = dbcParsed.messages.get(228);
|
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', () => {
|
test("DBC parses multi-line message comment", () => {
|
||||||
const dbcParsed = new DBC(DBC_BOARD_UNITS);
|
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
|
||||||
expect(dbcParsed.boardUnits[0].name).toEqual("first_board_unit");
|
const msg = dbcParsed.messages.get(228);
|
||||||
expect(dbcParsed.boardUnits[1].name).toEqual("second_board_unit");
|
|
||||||
|
expect(msg.comment).toEqual(
|
||||||
|
"this message contains\nsteer torque information"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DBC parses board unit comments', () => {
|
test("DBC parses board unit names", () => {
|
||||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
|
const dbcParsed = new DBC(DBC_BOARD_UNITS);
|
||||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
|
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', () => {
|
test("DBC parses board unit comments", () => {
|
||||||
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
|
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
|
||||||
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
|
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DBC parses signal value descriptions', () => {
|
test("DBC parses multi-line board unit comments", () => {
|
||||||
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
|
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
|
||||||
const {signals} = dbcParsed.messages.get(228);
|
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
|
||||||
|
|
||||||
const expectedTorqueRequestVals = new Map([['1', 'requesting torque'],
|
|
||||||
['0', 'not requesting torque']]);
|
|
||||||
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(expectedTorqueRequestVals);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DBC parses value tables', () => {
|
test("DBC parses signal value descriptions", () => {
|
||||||
const dbcParsed = new DBC(DBC_VALUE_TABLE);
|
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
|
||||||
const stateTableEntries = [['4', "DI_STATE_ENABLE"],
|
const { signals } = dbcParsed.messages.get(228);
|
||||||
['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 valueTableEntries = Array.from(dbcParsed.valueTables.entries());
|
const expectedTorqueRequestVals = new Map([
|
||||||
expect(valueTableEntries[0]).toEqual(['DI_state', stateTable]);
|
["1", "requesting torque"],
|
||||||
expect(valueTableEntries[1]).toEqual(['DI_speedUnits', speedUnitsTable]);
|
["0", "not requesting torque"]
|
||||||
|
]);
|
||||||
|
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(
|
||||||
|
expectedTorqueRequestVals
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('swapOrder properly converts little endian to big endian', () => {
|
test("DBC parses value tables", () => {
|
||||||
const littleEndianHex = 'e2d62a0bd0d3b5e5';
|
const dbcParsed = new DBC(DBC_VALUE_TABLE);
|
||||||
const bigEndianHex = 'e5b5d3d00b2ad6e2';
|
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);
|
const valueTableEntries = Array.from(dbcParsed.valueTables.entries());
|
||||||
|
expect(valueTableEntries[0]).toEqual(["DI_state", stateTable]);
|
||||||
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
|
expect(valueTableEntries[1]).toEqual(["DI_speedUnits", speedUnitsTable]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('int32 parser produces correct value for steer torque signal', () => {
|
test("swapOrder properly converts little endian to big endian", () => {
|
||||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
const littleEndianHex = "e2d62a0bd0d3b5e5";
|
||||||
|
const bigEndianHex = "e5b5d3d00b2ad6e2";
|
||||||
|
|
||||||
const hex = 'e2d62a0bd0d3b5e5';
|
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
|
||||||
const buffer = Buffer.from(hex, 'hex');
|
|
||||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
|
||||||
|
|
||||||
const bitArr = Bitarray.fromBytes(buffer);
|
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
|
||||||
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', () => {
|
test("int32 parser produces correct value for steer torque signal", () => {
|
||||||
const dbc = new DBC(DBC_MESSAGE_DEF);
|
const dbc = new DBC(DBC_MESSAGE_DEF);
|
||||||
|
|
||||||
const hex = 'e2d62a0bd0d3b5e5';
|
const hex = "e2d62a0bd0d3b5e5";
|
||||||
const value = dbc.valueForInt64Signal(steerTorqueSignal, hex);
|
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) {
|
function dbcInt32SignalValue(dbc, signalSpec, hex) {
|
||||||
const buffer = Buffer.from(hex, 'hex');
|
const buffer = Buffer.from(hex, "hex");
|
||||||
const bufferSwapped = Buffer.from(buffer).swap64();
|
const bufferSwapped = Buffer.from(buffer).swap64();
|
||||||
|
|
||||||
const bits = Bitarray.fromBytes(buffer);
|
const bits = Bitarray.fromBytes(buffer);
|
||||||
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
|
||||||
|
|
||||||
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
|
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DBC_BINARY_LE_SIGNAL = `
|
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
|
SG_ NEW_SIGNAL_1 : 37|1@1+ (1,0) [0|1] "" XXX
|
||||||
`;
|
`;
|
||||||
|
|
||||||
test('int32 parsers produces correct value for binary little endian signal', () => {
|
test("int32 parsers produces correct value for binary little endian signal", () => {
|
||||||
const dbc = new DBC(DBC_BINARY_LE_SIGNAL)
|
const dbc = new DBC(DBC_BINARY_LE_SIGNAL);
|
||||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||||
|
|
||||||
const hexDataSet = '0000000020000000';
|
const hexDataSet = "0000000020000000";
|
||||||
const hexDataNotSet = '0000000000000000';
|
const hexDataNotSet = "0000000000000000";
|
||||||
|
|
||||||
|
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
|
||||||
|
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
|
||||||
|
|
||||||
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
|
expect(setValue).toEqual(1);
|
||||||
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
|
expect(notSetValue).toEqual(0);
|
||||||
|
|
||||||
expect(setValue).toEqual(1);
|
|
||||||
expect(notSetValue).toEqual(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const DBC_TWO_BIT_LE_SIGNAL = `
|
const DBC_TWO_BIT_LE_SIGNAL = `
|
||||||
BO_ 768 NEW_MSG_1: 8 XXX
|
BO_ 768 NEW_MSG_1: 8 XXX
|
||||||
SG_ NEW_SIGNAL_1 : 35|2@1+ (1,0) [0|3] "" 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', () => {
|
test("int32 parser produces correct value for 2-bit little endian signal spanning words", () => {
|
||||||
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
|
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
|
||||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||||
|
|
||||||
const hexData = '00000001f8000000';
|
const hexData = "00000001f8000000";
|
||||||
|
|
||||||
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
|
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
|
||||||
expect(value).toEqual(3);
|
expect(value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
const DBC_FOUR_BIT_LE_SIGNAL = `
|
const DBC_FOUR_BIT_LE_SIGNAL = `
|
||||||
BO_ 768 NEW_MSG_1: 8 XXX
|
BO_ 768 NEW_MSG_1: 8 XXX
|
||||||
SG_ NEW_SIGNAL_1 : 6|4@1+ (1,0) [0|15] "" 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', () => {
|
test("int32 parser produces correct value for 4-bit little endian signal", () => {
|
||||||
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
|
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
|
||||||
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
|
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
|
||||||
|
|
||||||
// this data is symmetric, the data bits are 1111
|
// this data is symmetric, the data bits are 1111
|
||||||
const hexDataSymmetric = 'f00f000000000000';
|
const hexDataSymmetric = "f00f000000000000";
|
||||||
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
|
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
|
||||||
expect(symValue).toEqual(15);
|
expect(symValue).toEqual(15);
|
||||||
|
|
||||||
// this data is asymmetric, the data bits are 1101
|
// this data is asymmetric, the data bits are 1101
|
||||||
const hexDataAsymmetric = 'f002000000000000';
|
const hexDataAsymmetric = "f002000000000000";
|
||||||
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
|
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
|
||||||
expect(aSymValue).toEqual(11);
|
expect(aSymValue).toEqual(11);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const DBC_CHFFR_METRIC_COMMENT = `
|
const DBC_CHFFR_METRIC_COMMENT = `
|
||||||
BO_ 37 STEERING_CONTROL: 8 XXX
|
BO_ 37 STEERING_CONTROL: 8 XXX
|
||||||
SG_ STEER_ANGLE : 6|4@1+ (1,0) [0|15] "" 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";
|
CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180";
|
||||||
`;
|
`;
|
||||||
|
|
||||||
test('dbc parser parses top-level comment with chffr metric', () => {
|
test("dbc parser parses top-level comment with chffr metric", () => {
|
||||||
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
|
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
|
||||||
const { comments } = dbc;
|
const { comments } = dbc;
|
||||||
|
|
||||||
expect(comments.length).toEqual(1);
|
expect(comments.length).toEqual(1);
|
||||||
expect(comments[0]).toEqual("CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180");
|
expect(comments[0]).toEqual(
|
||||||
})
|
"CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
import DBC, {swapOrder} from '../../models/can/dbc';
|
import DBC, { swapOrder } from "../../models/can/dbc";
|
||||||
import { ACURA_DBC } from '../res/acura-dbc';
|
import { ACURA_DBC } from "../res/acura-dbc";
|
||||||
import { CRV_DBC } from '../res/crv-dbc';
|
import { CRV_DBC } from "../res/crv-dbc";
|
||||||
import { TESLA_DBC } from '../res/tesla-dbc';
|
import { TESLA_DBC } from "../res/tesla-dbc";
|
||||||
|
|
||||||
test('DBC.text() for acura DBC should be equivalent to its original text', () => {
|
test("DBC.text() for acura DBC should be equivalent to its original text", () => {
|
||||||
const dbc = new DBC(ACURA_DBC);
|
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', () => {
|
test("DBC.text() for crv DBC should be equivalent to its original text", () => {
|
||||||
const dbc = new DBC(CRV_DBC);
|
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', () => {
|
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 entries = [
|
||||||
const [segmentIdxLow, segmentIdxHi] = Entries.findSegmentIndices(entries, [3.45, 5.55]);
|
{ 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(segmentIdxLow).toBe(1);
|
||||||
expect(segmentIdxHi).toBe(entries.length - 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";
|
const FRAME_HEADER = "BO_ 255 SOME_FRAME: 5 ADAS";
|
||||||
|
|
||||||
test('Frame.header() returns spec compliant representation', () => {
|
test("Frame.header() returns spec compliant representation", () => {
|
||||||
const frame = new Frame({name: 'SOME_FRAME', id: 255, size: 5, transmitters: ['ADAS']});
|
const frame = new Frame({
|
||||||
expect(frame.header()).toEqual(FRAME_HEADER);
|
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 = {
|
const someSignalParams = {
|
||||||
name: 'STEER_TORQUE',
|
name: "STEER_TORQUE",
|
||||||
startBit: 7,
|
startBit: 7,
|
||||||
size: 16,
|
size: 16,
|
||||||
isLittleEndian: false,
|
isLittleEndian: false,
|
||||||
isSigned: true,
|
isSigned: true,
|
||||||
factor: 1,
|
factor: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
min: -3840,
|
min: -3840,
|
||||||
max: 3840,
|
max: 3840,
|
||||||
unit: ""};
|
unit: ""
|
||||||
|
};
|
||||||
|
|
||||||
const someOtherSignalParams = {
|
const someOtherSignalParams = {
|
||||||
name: 'DIFFERENT_NAME',
|
name: "DIFFERENT_NAME",
|
||||||
startBit: 0,
|
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,
|
size: 16,
|
||||||
isLittleEndian: false,
|
isLittleEndian: false
|
||||||
isSigned: true,
|
});
|
||||||
factor: 1,
|
|
||||||
offset: 0,
|
|
||||||
min: -3840,
|
|
||||||
max: 3840,
|
|
||||||
unit: ""};
|
|
||||||
|
|
||||||
test('Signal.equals returns true for signals with identical properties', () => {
|
const bitFifteenDescription = {
|
||||||
const someSignal = new Signal(someSignalParams);
|
bitNumber: 8,
|
||||||
const someEquivalentSignal = new Signal(someSignalParams);
|
isLsb: false,
|
||||||
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
|
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', () => {
|
test("Signal.bitDescription returns null for bit index that is not in its range", () => {
|
||||||
const someSignal = new Signal(someSignalParams);
|
const someSignal = new Signal({
|
||||||
const differentSignal = new Signal(someOtherSignalParams);
|
name: "some signal",
|
||||||
expect(someSignal.equals(differentSignal)).toBe(false);
|
startBit: 20,
|
||||||
});
|
size: 4,
|
||||||
|
isLittleEndian: false
|
||||||
test('Signal.bitDescription returns proper description for a little endian signal', () => {
|
});
|
||||||
const littleEndianSignal = new Signal({name: 'little endian signal',
|
|
||||||
startBit: 20,
|
expect(someSignal.bitDescription(21)).toBe(null);
|
||||||
size: 4,
|
expect(someSignal.bitDescription(16)).toBe(null);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,10 +5,10 @@ note: 'right' and 'left' in test descriptions
|
||||||
refer to the sides of the bit matrix
|
refer to the sides of the bit matrix
|
||||||
as displayed to the user.
|
as displayed to the user.
|
||||||
*/
|
*/
|
||||||
import AddSignals from '../../components/AddSignals';
|
import AddSignals from "../../components/AddSignals";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
import {StyleSheetTestUtils} from 'aphrodite';
|
import { StyleSheetTestUtils } from "aphrodite";
|
||||||
|
|
||||||
// Prevents style injection from firing after test finishes
|
// Prevents style injection from firing after test finishes
|
||||||
// and jsdom is torn down.
|
// and jsdom is torn down.
|
||||||
|
@ -22,286 +22,332 @@ afterEach(() => {
|
||||||
// signal creation
|
// signal creation
|
||||||
|
|
||||||
function createAddSignals(signals) {
|
function createAddSignals(signals) {
|
||||||
if(signals === undefined) {
|
if (signals === undefined) {
|
||||||
signals = {};
|
signals = {};
|
||||||
}
|
}
|
||||||
const message = {signals,
|
const message = {
|
||||||
address: 0,
|
signals,
|
||||||
entries: [
|
address: 0,
|
||||||
{relTime: 0,
|
entries: [
|
||||||
hexData: '0000000000000000'}
|
{
|
||||||
]};
|
relTime: 0,
|
||||||
|
hexData: "0000000000000000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
const component = shallow(<AddSignals
|
const component = shallow(
|
||||||
message={message}
|
<AddSignals
|
||||||
messageIndex={0}
|
message={message}
|
||||||
onConfirmedSignalChange={() => {}} />);
|
messageIndex={0}
|
||||||
|
onConfirmedSignalChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
test('double clicking adds a signal', () => {
|
test("double clicking adds a signal", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
|
|
||||||
const firstBit = component.find('.bit').first();
|
const firstBit = component.find(".bit").first();
|
||||||
|
|
||||||
firstBit.simulate('dblclick');
|
firstBit.simulate("dblclick");
|
||||||
const newSignal = Object.values(component.state('signals'))[0];
|
const newSignal = Object.values(component.state("signals"))[0];
|
||||||
|
|
||||||
expect(newSignal).toBeDefined();
|
expect(newSignal).toBeDefined();
|
||||||
expect(newSignal.size).toBe(1);
|
expect(newSignal.size).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dragging right to left across a byte creates a little endian signal', () => {
|
test("dragging right to left across a byte creates a little endian signal", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
const leftBitInByte = component.find('.bit').first();
|
const leftBitInByte = component.find(".bit").first();
|
||||||
const rightBitInByte = component.find('.bit').at(7);
|
const rightBitInByte = component.find(".bit").at(7);
|
||||||
rightBitInByte.simulate('mousedown');
|
rightBitInByte.simulate("mousedown");
|
||||||
leftBitInByte.simulate('mouseup');
|
leftBitInByte.simulate("mouseup");
|
||||||
|
|
||||||
const newSignal = Object.values(component.state('signals'))[0];
|
const newSignal = Object.values(component.state("signals"))[0];
|
||||||
expect(newSignal).toBeDefined();
|
expect(newSignal).toBeDefined();
|
||||||
expect(newSignal.size).toBe(8);
|
expect(newSignal.size).toBe(8);
|
||||||
expect(newSignal.isLittleEndian).toBe(true);
|
expect(newSignal.isLittleEndian).toBe(true);
|
||||||
expect(newSignal.startBit).toBe(0);
|
expect(newSignal.startBit).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dragging left to right across a byte creates a big endian signal', () => {
|
test("dragging left to right across a byte creates a big endian signal", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
const leftBitInByte = component.find('.bit').first();
|
const leftBitInByte = component.find(".bit").first();
|
||||||
const rightBitInByte = component.find('.bit').at(7);
|
const rightBitInByte = component.find(".bit").at(7);
|
||||||
leftBitInByte.simulate('mousedown');
|
leftBitInByte.simulate("mousedown");
|
||||||
rightBitInByte.simulate('mouseup');
|
rightBitInByte.simulate("mouseup");
|
||||||
|
|
||||||
const newSignal = Object.values(component.state('signals'))[0];
|
const newSignal = Object.values(component.state("signals"))[0];
|
||||||
expect(newSignal).toBeDefined();
|
expect(newSignal).toBeDefined();
|
||||||
expect(newSignal.size).toBe(8);
|
expect(newSignal.size).toBe(8);
|
||||||
expect(newSignal.isLittleEndian).toBe(false);
|
expect(newSignal.isLittleEndian).toBe(false);
|
||||||
expect(newSignal.startBit).toBe(7);
|
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', () => {
|
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 component = createAddSignals();
|
||||||
const leftBitInByte = component.find('.bit').first();
|
const leftBitInByte = component.find(".bit").first();
|
||||||
const rightBitInByte = component.find('.bit').at(15);
|
const rightBitInByte = component.find(".bit").at(15);
|
||||||
leftBitInByte.simulate('mousedown');
|
leftBitInByte.simulate("mousedown");
|
||||||
rightBitInByte.simulate('mouseup');
|
rightBitInByte.simulate("mouseup");
|
||||||
|
|
||||||
const newSignal = Object.values(component.state('signals'))[0];
|
const newSignal = Object.values(component.state("signals"))[0];
|
||||||
expect(newSignal).toBeDefined();
|
expect(newSignal).toBeDefined();
|
||||||
expect(newSignal.size).toBe(16);
|
expect(newSignal.size).toBe(16);
|
||||||
expect(newSignal.isLittleEndian).toBe(false);
|
expect(newSignal.isLittleEndian).toBe(false);
|
||||||
expect(newSignal.startBit).toBe(7);
|
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', () => {
|
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 component = createAddSignals();
|
||||||
const leftBitInByteOne = component.find('.bit').at(8); // left of byte 1
|
const leftBitInByteOne = component.find(".bit").at(8); // left of byte 1
|
||||||
const rightBitInByteZero = component.find('.bit').at(7); // right of byte 0
|
const rightBitInByteZero = component.find(".bit").at(7); // right of byte 0
|
||||||
|
|
||||||
rightBitInByteZero.simulate('mousedown');
|
rightBitInByteZero.simulate("mousedown");
|
||||||
leftBitInByteOne.simulate('mouseup');
|
leftBitInByteOne.simulate("mouseup");
|
||||||
|
|
||||||
const newSignal = Object.values(component.state('signals'))[0];
|
const newSignal = Object.values(component.state("signals"))[0];
|
||||||
expect(newSignal).toBeDefined();
|
expect(newSignal).toBeDefined();
|
||||||
expect(newSignal.size).toBe(16);
|
expect(newSignal.size).toBe(16);
|
||||||
expect(newSignal.isLittleEndian).toBe(true);
|
expect(newSignal.isLittleEndian).toBe(true);
|
||||||
expect(newSignal.startBit).toBe(0);
|
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', () => {
|
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 component = createAddSignals();
|
||||||
const leftBitInByteOne = component.find('.bit').at(8);
|
const leftBitInByteOne = component.find(".bit").at(8);
|
||||||
const rightBitInByteZero = component.find('.bit').at(7);
|
const rightBitInByteZero = component.find(".bit").at(7);
|
||||||
|
|
||||||
leftBitInByteOne.simulate('mousedown');
|
leftBitInByteOne.simulate("mousedown");
|
||||||
rightBitInByteZero.simulate('mouseup');
|
rightBitInByteZero.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(16);
|
expect(signal.size).toBe(16);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(0);
|
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', () => {
|
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 component = createAddSignals();
|
||||||
const leftBitInByteZero = component.find('.bit').at(0);
|
const leftBitInByteZero = component.find(".bit").at(0);
|
||||||
const rightBitInByteOne = component.find('.bit').at(15);
|
const rightBitInByteOne = component.find(".bit").at(15);
|
||||||
|
|
||||||
rightBitInByteOne.simulate('mousedown');
|
rightBitInByteOne.simulate("mousedown");
|
||||||
leftBitInByteZero.simulate('mouseup');
|
leftBitInByteZero.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(16);
|
expect(signal.size).toBe(16);
|
||||||
expect(signal.isLittleEndian).toBe(false);
|
expect(signal.isLittleEndian).toBe(false);
|
||||||
expect(signal.startBit).toBe(7);
|
expect(signal.startBit).toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
// signal mutation
|
// signal mutation
|
||||||
|
|
||||||
test('dragging a one-bit big-endian signal to the right should extend it to the right of the byte', () => {
|
test("dragging a one-bit big-endian signal to the right should extend it to the right of the byte", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: false});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 7, size: 1, isLittleEndian: false });
|
||||||
|
|
||||||
const signalBit = component.find('.bit').at(0);
|
const signalBit = component.find(".bit").at(0);
|
||||||
signalBit.simulate('mousedown');
|
signalBit.simulate("mousedown");
|
||||||
for(let i = 1; i < 8; i++) {
|
for (let i = 1; i < 8; i++) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
.at(i)
|
||||||
bitAtRightOfFirstByte.simulate('mouseup');
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||||
|
bitAtRightOfFirstByte.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(false);
|
expect(signal.isLittleEndian).toBe(false);
|
||||||
expect(signal.startBit).toBe(7);
|
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', () => {
|
test("dragging a one-bit little-endian signal to the right should extend it to the right of the byte", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: true});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 7, size: 1, isLittleEndian: true });
|
||||||
|
|
||||||
const signalBit = component.find('.bit').at(0);
|
const signalBit = component.find(".bit").at(0);
|
||||||
signalBit.simulate('mousedown');
|
signalBit.simulate("mousedown");
|
||||||
for(let i = 1; i < 8; i++) {
|
for (let i = 1; i < 8; i++) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
.at(i)
|
||||||
bitAtRightOfFirstByte.simulate('mouseup');
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||||
|
bitAtRightOfFirstByte.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(0);
|
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', () => {
|
test("dragging a one-bit big-endian signal to the left should extend it to the left of the byte", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 0, size: 1, isLittleEndian: false});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 0, size: 1, isLittleEndian: false });
|
||||||
|
|
||||||
const signalBit = component.find('.bit').at(7);
|
const signalBit = component.find(".bit").at(7);
|
||||||
signalBit.simulate('mousedown');
|
signalBit.simulate("mousedown");
|
||||||
for(let i = 6; i > -1; i--) {
|
for (let i = 6; i > -1; i--) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
const bitAtRightOfFirstByte = component.find('.bit').at(0);
|
.at(i)
|
||||||
bitAtRightOfFirstByte.simulate('mouseup');
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
const bitAtRightOfFirstByte = component.find(".bit").at(0);
|
||||||
|
bitAtRightOfFirstByte.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(false);
|
expect(signal.isLittleEndian).toBe(false);
|
||||||
expect(signal.startBit).toBe(7);
|
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', () => {
|
test("extending a two-bit big-endian signal by its LSB should extend it to the right of the byte", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 7, size: 2, isLittleEndian: false});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 7, size: 2, isLittleEndian: false });
|
||||||
|
|
||||||
const lsb = component.find('.bit').at(1);
|
const lsb = component.find(".bit").at(1);
|
||||||
lsb.simulate('mousedown');
|
lsb.simulate("mousedown");
|
||||||
for(let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
.at(i)
|
||||||
bitAtRightOfFirstByte.simulate('mouseup');
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||||
|
bitAtRightOfFirstByte.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(false);
|
expect(signal.isLittleEndian).toBe(false);
|
||||||
expect(signal.startBit).toBe(7);
|
expect(signal.startBit).toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a two-bit little-endian signal should extend by its LSB to the end of the byte', () => {
|
test("a two-bit little-endian signal should extend by its LSB to the end of the byte", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 6, size: 2, isLittleEndian: true});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 6, size: 2, isLittleEndian: true });
|
||||||
|
|
||||||
const lsb = component.find('.bit').at(1);
|
const lsb = component.find(".bit").at(1);
|
||||||
lsb.simulate('mousedown');
|
lsb.simulate("mousedown");
|
||||||
for(let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
const bitAtRightOfFirstByte = component.find('.bit').at(7);
|
.at(i)
|
||||||
bitAtRightOfFirstByte.simulate('mouseup');
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
const bitAtRightOfFirstByte = component.find(".bit").at(7);
|
||||||
|
bitAtRightOfFirstByte.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(0);
|
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', () => {
|
test("dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
|
||||||
|
|
||||||
const lsb = component.find('.bit').at(7);
|
const lsb = component.find(".bit").at(7);
|
||||||
lsb.simulate('mousedown');
|
lsb.simulate("mousedown");
|
||||||
|
|
||||||
const bitPastMsb = component.find('.bit').at(15);
|
const bitPastMsb = component.find(".bit").at(15);
|
||||||
bitPastMsb.simulate('mouseenter');
|
bitPastMsb.simulate("mouseenter");
|
||||||
bitPastMsb.simulate('mouseup');
|
bitPastMsb.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(0);
|
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', () => {
|
test("dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 7, size: 8, isLittleEndian: false});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 7, size: 8, isLittleEndian: false });
|
||||||
|
|
||||||
const lsb = component.find('.bit').at(7);
|
const lsb = component.find(".bit").at(7);
|
||||||
lsb.simulate('mousedown');
|
lsb.simulate("mousedown");
|
||||||
for(let i = 6; i > 0; i--) {
|
for (let i = 6; i > 0; i--) {
|
||||||
component.find('.bit').at(i).simulate('mouseenter');
|
component
|
||||||
}
|
.find(".bit")
|
||||||
component.find('.bit').at(1).simulate('mouseup');
|
.at(i)
|
||||||
|
.simulate("mouseenter");
|
||||||
|
}
|
||||||
|
component
|
||||||
|
.find(".bit")
|
||||||
|
.at(1)
|
||||||
|
.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(2);
|
expect(signal.size).toBe(2);
|
||||||
expect(signal.isLittleEndian).toBe(false);
|
expect(signal.isLittleEndian).toBe(false);
|
||||||
expect(signal.startBit).toBe(7);
|
expect(signal.startBit).toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a big endian signal spanning one byte should switch to little endian preserving its bit coverage', () => {
|
test("a big endian signal spanning one byte should switch to little endian preserving its bit coverage", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
|
||||||
|
|
||||||
const lsb = component.find('.bit').at(7);
|
const lsb = component.find(".bit").at(7);
|
||||||
lsb.simulate('mousedown');
|
lsb.simulate("mousedown");
|
||||||
|
|
||||||
const bitPastMsb = component.find('.bit').at(15);
|
const bitPastMsb = component.find(".bit").at(15);
|
||||||
bitPastMsb.simulate('mouseenter');
|
bitPastMsb.simulate("mouseenter");
|
||||||
bitPastMsb.simulate('mouseup');
|
bitPastMsb.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(8);
|
expect(signal.size).toBe(8);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(0);
|
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', () => {
|
test("dragging the msb of a 2-bit little endian signal to a lower byte should not change the signal", () => {
|
||||||
const component = createAddSignals();
|
const component = createAddSignals();
|
||||||
component.instance().createSignal({startBit: 14, size: 2, isLittleEndian: true});
|
component
|
||||||
|
.instance()
|
||||||
|
.createSignal({ startBit: 14, size: 2, isLittleEndian: true });
|
||||||
|
|
||||||
const msb = component.find('.bit').at(8);
|
const msb = component.find(".bit").at(8);
|
||||||
msb.simulate('mousedown');
|
msb.simulate("mousedown");
|
||||||
const bitOutOfBounds = component.find('.bit').at(0);
|
const bitOutOfBounds = component.find(".bit").at(0);
|
||||||
bitOutOfBounds.simulate('mouseenter');
|
bitOutOfBounds.simulate("mouseenter");
|
||||||
bitOutOfBounds.simulate('mouseup');
|
bitOutOfBounds.simulate("mouseup");
|
||||||
|
|
||||||
const signal = Object.values(component.state('signals'))[0];
|
const signal = Object.values(component.state("signals"))[0];
|
||||||
expect(signal).toBeDefined();
|
expect(signal).toBeDefined();
|
||||||
expect(signal.size).toBe(2);
|
expect(signal.size).toBe(2);
|
||||||
expect(signal.isLittleEndian).toBe(true);
|
expect(signal.isLittleEndian).toBe(true);
|
||||||
expect(signal.startBit).toBe(14);
|
expect(signal.startBit).toBe(14);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
import CanExplorer from '../../CanExplorer';
|
import CanExplorer from "../../CanExplorer";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
import {StyleSheetTestUtils} from 'aphrodite';
|
import { StyleSheetTestUtils } from "aphrodite";
|
||||||
|
|
||||||
test('CanExplorer renders', () => {
|
test("CanExplorer renders", () => {
|
||||||
const canExplorer = shallow(<CanExplorer />);
|
const canExplorer = shallow(<CanExplorer />);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import CanGraph from '../../components/CanGraph';
|
import CanGraph from "../../components/CanGraph";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('CanGraph successfully mounts with minimal default props', () => {
|
test("CanGraph successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<CanGraph
|
const component = shallow(
|
||||||
onGraphRefAvailable={() => {}}
|
<CanGraph
|
||||||
unplot={() => {}}
|
onGraphRefAvailable={() => {}}
|
||||||
messages={{}}
|
unplot={() => {}}
|
||||||
messageId={null}
|
messages={{}}
|
||||||
messageName={null}
|
messageId={null}
|
||||||
signalSpec={null}
|
messageName={null}
|
||||||
onSegmentChanged={() => {}}
|
signalSpec={null}
|
||||||
segment={[]}
|
onSegmentChanged={() => {}}
|
||||||
data={[]}
|
segment={[]}
|
||||||
onRelativeTimeClick={() => {}}
|
data={[]}
|
||||||
currentTime={0}
|
onRelativeTimeClick={() => {}}
|
||||||
onDragStart={() => {}}
|
currentTime={0}
|
||||||
onDragEnd={() => {}}
|
onDragStart={() => {}}
|
||||||
container={null}
|
onDragEnd={() => {}}
|
||||||
dragPos={null}
|
container={null}
|
||||||
canReceiveGraphDrop={false}
|
dragPos={null}
|
||||||
plottedSignals={[]}
|
canReceiveGraphDrop={false}
|
||||||
live={true}
|
plottedSignals={[]}
|
||||||
/>);
|
live={true}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import CanGraphList from '../../components/CanGraphList';
|
import CanGraphList from "../../components/CanGraphList";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('CanGraphList successfully mounts with minimal default props', () => {
|
test("CanGraphList successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<CanGraphList
|
const component = shallow(
|
||||||
plottedSignals={[]}
|
<CanGraphList
|
||||||
messages={{}}
|
plottedSignals={[]}
|
||||||
graphData={[]}
|
messages={{}}
|
||||||
onGraphTimeClick={() => {}}
|
graphData={[]}
|
||||||
seekTime={0}
|
onGraphTimeClick={() => {}}
|
||||||
onSegmentChanged={() => {}}
|
seekTime={0}
|
||||||
onSignalUnplotPressed={() => {}}
|
onSegmentChanged={() => {}}
|
||||||
segment={[]}
|
onSignalUnplotPressed={() => {}}
|
||||||
mergePlots={() => {}}
|
segment={[]}
|
||||||
live={true} />);
|
mergePlots={() => {}}
|
||||||
expect(component.exists()).toBe(true);
|
live={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import CanLog from '../../components/CanLog';
|
import CanLog from "../../components/CanLog";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('CanLog successfully mounts with minimal default props', () => {
|
test("CanLog successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<CanLog
|
const component = shallow(
|
||||||
message={null}
|
<CanLog
|
||||||
messageIndex={0}
|
message={null}
|
||||||
segmentIndices={[]}
|
messageIndex={0}
|
||||||
plottedSignals={[]}
|
segmentIndices={[]}
|
||||||
onSignalPlotPressed={() => {}}
|
plottedSignals={[]}
|
||||||
onSignalUnplotPressed={() => {}}
|
onSignalPlotPressed={() => {}}
|
||||||
showAddSignal={() => {}}
|
onSignalUnplotPressed={() => {}}
|
||||||
onMessageExpanded={() => {}} />);
|
showAddSignal={() => {}}
|
||||||
expect(component.exists()).toBe(true);
|
onMessageExpanded={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import DbcUpload from '../../components/DbcUpload';
|
import DbcUpload from "../../components/DbcUpload";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('DbcUpload successfully mounts with minimal default props', () => {
|
test("DbcUpload successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<DbcUpload />);
|
const component = shallow(<DbcUpload />);
|
||||||
expect(component.exists()).toBe(true);
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import EditMessageModal from '../../components/EditMessageModal';
|
import EditMessageModal from "../../components/EditMessageModal";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
import DbcUtils from '../../utils/dbc';
|
import DbcUtils from "../../utils/dbc";
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
|
|
||||||
test('EditMessageModal successfully mounts with minimal default props', () => {
|
test("EditMessageModal successfully mounts with minimal default props", () => {
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
const frame = dbc.createFrame(0);
|
const frame = dbc.createFrame(0);
|
||||||
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 1);
|
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 1);
|
||||||
|
|
||||||
const component = shallow(<EditMessageModal
|
const component = shallow(
|
||||||
handleClose={() => {}}
|
<EditMessageModal
|
||||||
handleSave={() => {}}
|
handleClose={() => {}}
|
||||||
message={message}
|
handleSave={() => {}}
|
||||||
/>);
|
message={message}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import Explorer from '../../components/Explorer';
|
import Explorer from "../../components/Explorer";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Moment from 'moment';
|
import Moment from "moment";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('Explorer successfully mounts with minimal default props', () => {
|
test("Explorer successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<Explorer
|
const component = shallow(
|
||||||
url={null}
|
<Explorer
|
||||||
live={true}
|
url={null}
|
||||||
messages={{}}
|
live={true}
|
||||||
selectedMessage={null}
|
messages={{}}
|
||||||
onConfirmedSignalChange={() => {}}
|
selectedMessage={null}
|
||||||
onSeek={() => {}}
|
onConfirmedSignalChange={() => {}}
|
||||||
onUserSeek={() => {}}
|
onSeek={() => {}}
|
||||||
canFrameOffset={0}
|
onUserSeek={() => {}}
|
||||||
firstCanTime={0}
|
canFrameOffset={0}
|
||||||
seekTime={0}
|
firstCanTime={0}
|
||||||
seekIndex={0}
|
seekTime={0}
|
||||||
currentParts={[0,0]}
|
seekIndex={0}
|
||||||
partsLoaded={0}
|
currentParts={[0, 0]}
|
||||||
autoplay={true}
|
partsLoaded={0}
|
||||||
showEditMessageModal={() => {}}
|
autoplay={true}
|
||||||
onPartChange={() => {}}
|
showEditMessageModal={() => {}}
|
||||||
routeStartTime={Moment()}
|
onPartChange={() => {}}
|
||||||
partsCount={0}
|
routeStartTime={Moment()}
|
||||||
/>);
|
partsCount={0}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import GithubDbcList from '../../components/GithubDbcList';
|
import GithubDbcList from "../../components/GithubDbcList";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
import OpenDbc from '../../api/OpenDbc';
|
import OpenDbc from "../../api/OpenDbc";
|
||||||
|
|
||||||
test('GithubDbcList successfully mounts with minimal default props', () => {
|
test("GithubDbcList successfully mounts with minimal default props", () => {
|
||||||
const openDbcClient = new OpenDbc(null);
|
const openDbcClient = new OpenDbc(null);
|
||||||
|
|
||||||
const component = shallow(<GithubDbcList
|
const component = shallow(
|
||||||
onDbcLoaded={ () => {} }
|
<GithubDbcList
|
||||||
repo="commaai/opendbc"
|
onDbcLoaded={() => {}}
|
||||||
openDbcClient={ openDbcClient } />);
|
repo="commaai/opendbc"
|
||||||
expect(component.exists()).toBe(true);
|
openDbcClient={openDbcClient}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import HLS from '../../components/HLS';
|
import HLS from "../../components/HLS";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('HLS successfully mounts with minimal default props', () => {
|
test("HLS successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<HLS
|
const component = shallow(
|
||||||
source={'http://comma.ai'}
|
<HLS
|
||||||
startTime={0}
|
source={"http://comma.ai"}
|
||||||
playbackSpeed={1}
|
startTime={0}
|
||||||
onVideoElementAvailable={() => {}}
|
playbackSpeed={1}
|
||||||
playing={false}
|
onVideoElementAvailable={() => {}}
|
||||||
onClick={() => {}}
|
playing={false}
|
||||||
onLoadStart={() => {}}
|
onClick={() => {}}
|
||||||
onLoadEnd={() => {}}
|
onLoadStart={() => {}}
|
||||||
onUserSeek={() => {}}
|
onLoadEnd={() => {}}
|
||||||
onPlaySeek={() => {}}
|
onUserSeek={() => {}}
|
||||||
segmentProgress={() => {}}
|
onPlaySeek={() => {}}
|
||||||
shouldRestart={false}
|
segmentProgress={() => {}}
|
||||||
onRestart={() => {}} />);
|
shouldRestart={false}
|
||||||
expect(component.exists()).toBe(true);
|
onRestart={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
import LoadDbcModal from '../../components/LoadDbcModal';
|
import LoadDbcModal from "../../components/LoadDbcModal";
|
||||||
import OpenDbc from '../../api/OpenDbc';
|
import OpenDbc from "../../api/OpenDbc";
|
||||||
|
|
||||||
test('LoadDbcModal successfully mounts with minimal default props', () => {
|
test("LoadDbcModal successfully mounts with minimal default props", () => {
|
||||||
const openDbcClient = new OpenDbc(null);
|
const openDbcClient = new OpenDbc(null);
|
||||||
const component = shallow(<LoadDbcModal
|
const component = shallow(
|
||||||
onDbcSelected={() => {}}
|
<LoadDbcModal
|
||||||
handleClose={() => {}}
|
onDbcSelected={() => {}}
|
||||||
openDbcClient={openDbcClient}
|
handleClose={() => {}}
|
||||||
loginWithGithub={<p>Login with github</p>}
|
openDbcClient={openDbcClient}
|
||||||
/>);
|
loginWithGithub={<p>Login with github</p>}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import LoadingBar from '../../components/LoadingBar';
|
import LoadingBar from "../../components/LoadingBar";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('LoadingBar successfully mounts with minimal default props', () => {
|
test("LoadingBar successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<LoadingBar />);
|
const component = shallow(<LoadingBar />);
|
||||||
expect(component.exists()).toBe(true);
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
import MessageBytes from '../../components/MessageBytes';
|
import MessageBytes from "../../components/MessageBytes";
|
||||||
import DbcUtils from '../../utils/dbc';
|
import DbcUtils from "../../utils/dbc";
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
|
|
||||||
test('MessageBytes successfully mounts with minimal default props', () => {
|
test("MessageBytes successfully mounts with minimal default props", () => {
|
||||||
const message = DbcUtils.createMessageSpec(new DBC(), 0, '0', 1);
|
const message = DbcUtils.createMessageSpec(new DBC(), 0, "0", 1);
|
||||||
const component = shallow(<MessageBytes
|
const component = shallow(
|
||||||
seekTime={0}
|
<MessageBytes seekTime={0} message={message} live={true} />
|
||||||
message={message}
|
);
|
||||||
live={true} />);
|
expect(component.exists()).toBe(true);
|
||||||
expect(component.exists()).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import Meta from '../../components/Meta';
|
import Meta from "../../components/Meta";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('Meta successfully mounts with minimal default props', () => {
|
test("Meta successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<Meta url={null}
|
const component = shallow(
|
||||||
messages={{}}
|
<Meta
|
||||||
selectedMessages={[]}
|
url={null}
|
||||||
updateSelectedMessages={() => {}}
|
messages={{}}
|
||||||
showEditMessageModal={() => {}}
|
selectedMessages={[]}
|
||||||
currentParts={[]}
|
updateSelectedMessages={() => {}}
|
||||||
onMessageSelected={() => {}}
|
showEditMessageModal={() => {}}
|
||||||
onMessageUnselected={() => {}}
|
currentParts={[]}
|
||||||
showLoadDbc={() => {}}
|
onMessageSelected={() => {}}
|
||||||
showSaveDbc={() => {}}
|
onMessageUnselected={() => {}}
|
||||||
dbcFilename={null}
|
showLoadDbc={() => {}}
|
||||||
dbcLastSaved={null}
|
showSaveDbc={() => {}}
|
||||||
dongleId={null}
|
dbcFilename={null}
|
||||||
name={null}
|
dbcLastSaved={null}
|
||||||
route={null}
|
dongleId={null}
|
||||||
seekTime={0}
|
name={null}
|
||||||
seekIndex={0}
|
route={null}
|
||||||
maxByteStateChangeCount={0}
|
seekTime={0}
|
||||||
isDemo={false}
|
seekIndex={0}
|
||||||
live={true}
|
maxByteStateChangeCount={0}
|
||||||
/>);
|
isDemo={false}
|
||||||
expect(component.exists()).toBe(true);
|
live={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import Modal from '../../components/Modal';
|
import Modal from "../../components/Modal";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('Modal successfully mounts with minimal default props', () => {
|
test("Modal successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<Modal />);
|
const component = shallow(<Modal />);
|
||||||
expect(component.exists()).toBe(true);
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import PartSelector from '../../components/PartSelector';
|
import PartSelector from "../../components/PartSelector";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('PartSelector successfully mounts with minimal default props', () => {
|
test("PartSelector successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<PartSelector
|
const component = shallow(
|
||||||
onPartChange={ () => {} }
|
<PartSelector onPartChange={() => {}} partsCount={0} />
|
||||||
partsCount={ 0 } />);
|
);
|
||||||
expect(component.exists()).toBe(true);
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import PlayButton from '../../components/PlayButton';
|
import PlayButton from "../../components/PlayButton";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('PlayButton successfully mounts with minimal default props', () => {
|
test("PlayButton successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<PlayButton />);
|
const component = shallow(<PlayButton />);
|
||||||
expect(component.exists()).toBe(true);
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import RouteSeeker from '../../components/RouteSeeker';
|
import RouteSeeker from "../../components/RouteSeeker";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('RouteSeeker successfully mounts with minimal default props', () => {
|
test("RouteSeeker successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<RouteSeeker
|
const component = shallow(
|
||||||
nearestFrameTime={0}
|
<RouteSeeker
|
||||||
segmentProgress={() => {}}
|
nearestFrameTime={0}
|
||||||
secondsLoaded={0}
|
segmentProgress={() => {}}
|
||||||
segmentIndices={[]}
|
secondsLoaded={0}
|
||||||
onUserSeek={() => {}}
|
segmentIndices={[]}
|
||||||
onPlaySeek={() => {}}
|
onUserSeek={() => {}}
|
||||||
videoElement={null}
|
onPlaySeek={() => {}}
|
||||||
onPlay={() => {}}
|
videoElement={null}
|
||||||
onPause={() => {}}
|
onPlay={() => {}}
|
||||||
playing={false}
|
onPause={() => {}}
|
||||||
ratioTime={() => {}} />);
|
playing={false}
|
||||||
expect(component.exists()).toBe(true);
|
ratioTime={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import RouteVideoSync from '../../components/RouteVideoSync';
|
import RouteVideoSync from "../../components/RouteVideoSync";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
import {StyleSheetTestUtils} from 'aphrodite';
|
import { StyleSheetTestUtils } from "aphrodite";
|
||||||
|
|
||||||
// Prevents style injection from firing after test finishes
|
// Prevents style injection from firing after test finishes
|
||||||
// and jsdom is torn down.
|
// and jsdom is torn down.
|
||||||
|
@ -14,22 +14,25 @@ afterEach(() => {
|
||||||
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
|
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('RouteVideoSync successfully mounts with minimal default props', () => {
|
test("RouteVideoSync successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<RouteVideoSync
|
const component = shallow(
|
||||||
message={null}
|
<RouteVideoSync
|
||||||
secondsLoaded={0}
|
message={null}
|
||||||
startOffset={0}
|
secondsLoaded={0}
|
||||||
seekIndex={0}
|
startOffset={0}
|
||||||
userSeekIndex={0}
|
seekIndex={0}
|
||||||
playing={false}
|
userSeekIndex={0}
|
||||||
url={'http://comma.ai'}
|
playing={false}
|
||||||
canFrameOffset={0}
|
url={"http://comma.ai"}
|
||||||
firstCanTime={0}
|
canFrameOffset={0}
|
||||||
onVideoClick={() => {}}
|
firstCanTime={0}
|
||||||
onPlaySeek={() => {}}
|
onVideoClick={() => {}}
|
||||||
onUserSeek={() => {}}
|
onPlaySeek={() => {}}
|
||||||
onPlay={() => {}}
|
onUserSeek={() => {}}
|
||||||
onPause={() => {}}
|
onPlay={() => {}}
|
||||||
userSeekTime={0} />);
|
onPause={() => {}}
|
||||||
expect(component.exists()).toBe(true);
|
userSeekTime={0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
import SaveDbcModal from '../../components/SaveDbcModal';
|
import SaveDbcModal from "../../components/SaveDbcModal";
|
||||||
import OpenDbc from '../../api/OpenDbc';
|
import OpenDbc from "../../api/OpenDbc";
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
|
|
||||||
test('SaveDbcModal successfully mounts with minimal default props', () => {
|
test("SaveDbcModal successfully mounts with minimal default props", () => {
|
||||||
const openDbcClient = new OpenDbc(null);
|
const openDbcClient = new OpenDbc(null);
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
const component = shallow(<SaveDbcModal
|
const component = shallow(
|
||||||
dbc={dbc}
|
<SaveDbcModal
|
||||||
sourceDbcFilename={''}
|
dbc={dbc}
|
||||||
onDbcSaved={() => {}}
|
sourceDbcFilename={""}
|
||||||
handleClose={() => {}}
|
onDbcSaved={() => {}}
|
||||||
openDbcClient={openDbcClient}
|
handleClose={() => {}}
|
||||||
hasGithubAuth={false}
|
openDbcClient={openDbcClient}
|
||||||
loginWithGithub={<p>Login with github</p>}
|
hasGithubAuth={false}
|
||||||
/>);
|
loginWithGithub={<p>Login with github</p>}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
global.__JEST__ = 1
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import SignalLegend from '../../components/SignalLegend';
|
import SignalLegend from "../../components/SignalLegend";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
|
|
||||||
test('SignalLegend successfully mounts with minimal default props', () => {
|
test("SignalLegend successfully mounts with minimal default props", () => {
|
||||||
const component = shallow(<SignalLegend
|
const component = shallow(
|
||||||
signals={{}}
|
<SignalLegend
|
||||||
signalStyles={{}}
|
signals={{}}
|
||||||
highlightedSignal={null}
|
signalStyles={{}}
|
||||||
onSignalHover={() => {}}
|
highlightedSignal={null}
|
||||||
onSignalHoverEnd={() => {}}
|
onSignalHover={() => {}}
|
||||||
onTentativeSignalChange={() => {}}
|
onSignalHoverEnd={() => {}}
|
||||||
onSignalChange={() => {}}
|
onTentativeSignalChange={() => {}}
|
||||||
onSignalRemove={() => {}}
|
onSignalChange={() => {}}
|
||||||
onSignalPlotChange={() => {}}
|
onSignalRemove={() => {}}
|
||||||
plottedSignals={[]}
|
onSignalPlotChange={() => {}}
|
||||||
/>);
|
plottedSignals={[]}
|
||||||
expect(component.exists()).toBe(true);
|
/>
|
||||||
|
);
|
||||||
|
expect(component.exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import SignalLegendEntry from '../../components/SignalLegendEntry';
|
import SignalLegendEntry from "../../components/SignalLegendEntry";
|
||||||
import Signal from '../../models/can/signal';
|
import Signal from "../../models/can/signal";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { shallow, mount, render } from 'enzyme';
|
import { shallow, mount, render } from "enzyme";
|
||||||
import {StyleSheetTestUtils} from 'aphrodite';
|
import { StyleSheetTestUtils } from "aphrodite";
|
||||||
|
|
||||||
// Prevents style injection from firing after test finishes
|
// Prevents style injection from firing after test finishes
|
||||||
// and jsdom is torn down.
|
// and jsdom is torn down.
|
||||||
|
@ -14,100 +14,122 @@ afterEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function createSignalLegendEntry(props) {
|
function createSignalLegendEntry(props) {
|
||||||
let signal = props.signal,
|
let signal = props.signal,
|
||||||
onSignalChange = props.onSignalChange,
|
onSignalChange = props.onSignalChange,
|
||||||
onTentativeSignalChange = props.onTentativeSignalChange;
|
onTentativeSignalChange = props.onTentativeSignalChange;
|
||||||
if(signal === undefined) {
|
if (signal === undefined) {
|
||||||
signal = new Signal({name: 'NEW_SIGNAL'});
|
signal = new Signal({ name: "NEW_SIGNAL" });
|
||||||
}
|
}
|
||||||
if(onSignalChange === undefined) {
|
if (onSignalChange === undefined) {
|
||||||
onSignalChange = () => {};
|
onSignalChange = () => {};
|
||||||
}
|
}
|
||||||
if(onTentativeSignalChange === undefined) {
|
if (onTentativeSignalChange === undefined) {
|
||||||
onTentativeSignalChange = () => {};
|
onTentativeSignalChange = () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return shallow(<SignalLegendEntry
|
return shallow(
|
||||||
highlightedStyle={null}
|
<SignalLegendEntry
|
||||||
signal={signal}
|
highlightedStyle={null}
|
||||||
onSignalChange={onSignalChange}
|
signal={signal}
|
||||||
onTentativeSignalChange={onTentativeSignalChange}
|
onSignalChange={onSignalChange}
|
||||||
/>);
|
onTentativeSignalChange={onTentativeSignalChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('a little endian signal spanning one byte should adjust its startBit switching to big endian, preserving its bit coverage', () => {
|
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',
|
const signal = new Signal({
|
||||||
startBit: 0,
|
name: "signal",
|
||||||
size: 8,
|
startBit: 0,
|
||||||
isLittleEndian: true});
|
size: 8,
|
||||||
|
isLittleEndian: true
|
||||||
|
});
|
||||||
|
|
||||||
const component = createSignalLegendEntry({signal});
|
const component = createSignalLegendEntry({ signal });
|
||||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||||
component.instance().updateField(endiannessFieldSpec, false);
|
"isLittleEndian"
|
||||||
|
);
|
||||||
|
component.instance().updateField(endiannessFieldSpec, false);
|
||||||
|
|
||||||
const signalEdited = component.state('signalEdited');
|
const signalEdited = component.state("signalEdited");
|
||||||
expect(signalEdited.isLittleEndian).toBe(false);
|
expect(signalEdited.isLittleEndian).toBe(false);
|
||||||
expect(signalEdited.startBit).toBe(7);
|
expect(signalEdited.startBit).toBe(7);
|
||||||
expect(signalEdited.size).toBe(8);
|
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', () => {
|
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',
|
const signal = new Signal({
|
||||||
startBit: 7,
|
name: "signal",
|
||||||
size: 8,
|
startBit: 7,
|
||||||
isLittleEndian: false});
|
size: 8,
|
||||||
const component = createSignalLegendEntry({signal});
|
isLittleEndian: false
|
||||||
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');
|
const signalEdited = component.state("signalEdited");
|
||||||
expect(signalEdited.isLittleEndian).toBe(true);
|
expect(signalEdited.isLittleEndian).toBe(true);
|
||||||
expect(signalEdited.startBit).toBe(0);
|
expect(signalEdited.startBit).toBe(0);
|
||||||
expect(signalEdited.size).toBe(8);
|
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", () => {
|
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',
|
const signal = new Signal({
|
||||||
startBit: 7,
|
name: "signal",
|
||||||
size: 12,
|
startBit: 7,
|
||||||
isLittleEndian: false});
|
size: 12,
|
||||||
|
isLittleEndian: false
|
||||||
|
});
|
||||||
|
|
||||||
const component = createSignalLegendEntry({signal});
|
const component = createSignalLegendEntry({ signal });
|
||||||
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
|
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
|
||||||
component.instance().updateField(endiannessFieldSpec, true);
|
"isLittleEndian"
|
||||||
|
);
|
||||||
|
component.instance().updateField(endiannessFieldSpec, true);
|
||||||
|
|
||||||
const signalEdited = component.state('signalEdited');
|
const signalEdited = component.state("signalEdited");
|
||||||
expect(signalEdited.isLittleEndian).toBe(true);
|
expect(signalEdited.isLittleEndian).toBe(true);
|
||||||
expect(signalEdited.startBit).toBe(0);
|
expect(signalEdited.startBit).toBe(0);
|
||||||
expect(signalEdited.size).toBe(12);
|
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', () => {
|
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',
|
const signal = new Signal({
|
||||||
startBit: 13,
|
name: "signal",
|
||||||
size: 3,
|
startBit: 13,
|
||||||
isLittleEndian: true});
|
size: 3,
|
||||||
const component = createSignalLegendEntry({signal});
|
isLittleEndian: true
|
||||||
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');
|
const signalEdited = component.state("signalEdited");
|
||||||
expect(signalEdited.isLittleEndian).toBe(false);
|
expect(signalEdited.isLittleEndian).toBe(false);
|
||||||
expect(signalEdited.startBit).toBe(15);
|
expect(signalEdited.startBit).toBe(15);
|
||||||
expect(signalEdited.size).toBe(3);
|
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', () => {
|
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',
|
const signal = new Signal({
|
||||||
startBit: 15,
|
name: "signal",
|
||||||
size: 3,
|
startBit: 15,
|
||||||
isLittleEndian: false});
|
size: 3,
|
||||||
const component = createSignalLegendEntry({signal});
|
isLittleEndian: false
|
||||||
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');
|
const signalEdited = component.state("signalEdited");
|
||||||
expect(signalEdited.isLittleEndian).toBe(true);
|
expect(signalEdited.isLittleEndian).toBe(true);
|
||||||
expect(signalEdited.startBit).toBe(13);
|
expect(signalEdited.startBit).toBe(13);
|
||||||
expect(signalEdited.size).toBe(3);
|
expect(signalEdited.size).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,38 +1,45 @@
|
||||||
// appendNewGraphData(plottedSignals, graphData, messages) {
|
// appendNewGraphData(plottedSignals, graphData, messages) {
|
||||||
global.__JEST__ = 1;
|
global.__JEST__ = 1;
|
||||||
|
|
||||||
import GraphData from '../../models/graph-data';
|
import GraphData from "../../models/graph-data";
|
||||||
import Signal from '../../models/can/signal';
|
import Signal from "../../models/can/signal";
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
import DbcUtils from '../../utils/dbc';
|
import DbcUtils from "../../utils/dbc";
|
||||||
|
|
||||||
function appendMockGraphData(existingGraphData, entryCount = 1) {
|
function appendMockGraphData(existingGraphData, entryCount = 1) {
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
const signal = new Signal({name: 'NEW_SIGNAL_1'});
|
const signal = new Signal({ name: "NEW_SIGNAL_1" });
|
||||||
dbc.setSignals(0, {[signal.name]: signal});
|
dbc.setSignals(0, { [signal.name]: signal });
|
||||||
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 0);
|
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 0);
|
||||||
// time, relTime, data, byteStateChangeTimes) {
|
// time, relTime, data, byteStateChangeTimes) {
|
||||||
message.entries = Array(entryCount).fill(DbcUtils.createMessageEntry(dbc, 0, 0, 0, Buffer.alloc(8), []));
|
message.entries = Array(entryCount).fill(
|
||||||
const messages = {[message.id]: message};
|
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', () => {
|
test("GraphData.appendNewGraphData adds messages to empty GraphData array", () => {
|
||||||
const graphData = appendMockGraphData([[]]);
|
const graphData = appendMockGraphData([[]]);
|
||||||
expect(graphData.length).toEqual(1); // 1 plot
|
expect(graphData.length).toEqual(1); // 1 plot
|
||||||
expect(graphData[0].length).toEqual(1); // 1 message entry
|
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
|
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', () => {
|
test("GraphData.appendNewGraphData does not change graph data when entries are unchanged", () => {
|
||||||
let graphData = [[]];
|
let graphData = [[]];
|
||||||
for(let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
graphData = appendMockGraphData(graphData);
|
graphData = appendMockGraphData(graphData);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(graphData.length).toEqual(1);
|
expect(graphData.length).toEqual(1);
|
||||||
expect(graphData[0].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) {
|
function arrayBufferFromHex(hex) {
|
||||||
const buffer = Buffer.from(hex, 'hex');
|
const buffer = Buffer.from(hex, "hex");
|
||||||
const arrayBuffer = new ArrayBuffer(buffer.length);
|
const arrayBuffer = new ArrayBuffer(buffer.length);
|
||||||
const view = new Uint8Array(arrayBuffer);
|
const view = new Uint8Array(arrayBuffer);
|
||||||
for(let i = 0; i < buffer.length; i++) {
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
view[i] = buffer[i];
|
view[i] = buffer[i];
|
||||||
}
|
}
|
||||||
return arrayBuffer;
|
return arrayBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('parseCanBuffer correctly parses a message', () => {
|
test("parseCanBuffer correctly parses a message", () => {
|
||||||
const panda = new Panda();
|
const panda = new Panda();
|
||||||
// 16 byte buffer
|
// 16 byte buffer
|
||||||
|
|
||||||
const arrayBuffer = arrayBufferFromHex('abababababababababababababababab');
|
const arrayBuffer = arrayBufferFromHex("abababababababababababababababab");
|
||||||
|
|
||||||
const messages = panda.parseCanBuffer(arrayBuffer);
|
const messages = panda.parseCanBuffer(arrayBuffer);
|
||||||
expect(messages.length).toEqual(1)
|
expect(messages.length).toEqual(1);
|
||||||
expect(messages[0]).toEqual([1373, 43947, 'abababababababab', 10]);
|
expect(messages[0]).toEqual([1373, 43947, "abababababababab", 10]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const ACURA_DBC= `VERSION ""
|
export const ACURA_DBC = `VERSION ""
|
||||||
|
|
||||||
|
|
||||||
NS_ :
|
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_ 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_ 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" ;
|
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_steeringAngleRequest 16384 "ZERO_ANGLE" ;
|
||||||
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
|
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
|
||||||
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
|
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)', () => {
|
test("Shade darkens rgb white (255,255,255)", () => {
|
||||||
const rgb = [255,255,255];
|
const rgb = [255, 255, 255];
|
||||||
const darkenRgb = shade(rgb, -0.5);
|
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 DbcUtils from "../../utils/dbc";
|
||||||
import DBC from '../../models/can/dbc';
|
import DBC from "../../models/can/dbc";
|
||||||
import Signal from '../../models/can/signal';
|
import Signal from "../../models/can/signal";
|
||||||
|
|
||||||
// want to mock pandareader and test processStreamedCanMessages
|
// want to mock pandareader and test processStreamedCanMessages
|
||||||
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from('abababababababab', 'hex'), 1];
|
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from("abababababababab", "hex"), 1];
|
||||||
const SAMPLE_MESSAGE_ID = '1:10';
|
const SAMPLE_MESSAGE_ID = "1:10";
|
||||||
|
|
||||||
function expectSampleMessageFieldsPreserved(messages, frame) {
|
function expectSampleMessageFieldsPreserved(messages, frame) {
|
||||||
const [address, busTime, data, source] = SAMPLE_MESSAGE;
|
const [address, busTime, data, source] = SAMPLE_MESSAGE;
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
|
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
|
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
|
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
|
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(Array(8).fill(0));
|
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(
|
||||||
|
Array(8).fill(0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
|
// function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
|
||||||
function addMessages(messages, message, dbc, n) {
|
function addMessages(messages, message, dbc, n) {
|
||||||
const firstCanTime = 0;
|
const firstCanTime = 0;
|
||||||
message = [...message];
|
message = [...message];
|
||||||
let nextMessage = () => {message[1] = message[1] + 1; return message};
|
let nextMessage = () => {
|
||||||
|
message[1] = message[1] + 1;
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
for(let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
|
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test('addCanMessage should add raw can message with empty dbc', () => {
|
test("addCanMessage should add raw can message with empty dbc", () => {
|
||||||
const messages = {};
|
const messages = {};
|
||||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
|
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
|
||||||
|
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
|
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
|
||||||
expectSampleMessageFieldsPreserved(messages);
|
expectSampleMessageFieldsPreserved(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addCanMessage should add multiple raw can messages with empty dbc', () => {
|
test("addCanMessage should add multiple raw can messages with empty dbc", () => {
|
||||||
const messages = {};
|
const messages = {};
|
||||||
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
|
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
|
||||||
|
|
||||||
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
|
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
|
||||||
expectSampleMessageFieldsPreserved(messages);
|
expectSampleMessageFieldsPreserved(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addCanMessage should add parsed can message with dbc containing message spec', () => {
|
test("addCanMessage should add parsed can message with dbc containing message spec", () => {
|
||||||
const messages = {};
|
const messages = {};
|
||||||
// create dbc with message spec and signal for sample_message
|
// create dbc with message spec and signal for sample_message
|
||||||
const dbc = new DBC();
|
const dbc = new DBC();
|
||||||
dbc.createFrame(SAMPLE_MESSAGE[0]);
|
dbc.createFrame(SAMPLE_MESSAGE[0]);
|
||||||
const signal = new Signal({name: 'NEW_SIGNAL', startBit: 0, size: 8});
|
const signal = new Signal({ name: "NEW_SIGNAL", startBit: 0, size: 8 });
|
||||||
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
|
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
|
||||||
|
|
||||||
// add 1 sample_message
|
// add 1 sample_message
|
||||||
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
|
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
|
||||||
|
|
||||||
// verify message and parsed signal added
|
// verify message and parsed signal added
|
||||||
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
|
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
|
||||||
expect(sampleMessages.entries.length).toEqual(1);
|
expect(sampleMessages.entries.length).toEqual(1);
|
||||||
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
|
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
|
||||||
expectSampleMessageFieldsPreserved(messages, dbc.messages.get(SAMPLE_MESSAGE[0]));
|
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(`
|
const AcuraDbc = new DBC(`
|
||||||
VERSION ""
|
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 {
|
export default class OpenDBC {
|
||||||
constructor(token) {
|
constructor(token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.github = new GitHub({token});
|
this.github = new GitHub({ token });
|
||||||
this.sourceRepo = this.github.getRepo('commaai', 'opendbc');
|
this.sourceRepo = this.github.getRepo("commaai", "opendbc");
|
||||||
this.githubUsername = null;
|
this.githubUsername = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ export default class OpenDBC {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGithubUsername() {
|
async getGithubUsername() {
|
||||||
if(this.githubUsername) {
|
if (this.githubUsername) {
|
||||||
return this.githubUsername;
|
return this.githubUsername;
|
||||||
} else {
|
} else {
|
||||||
const githubUsername = await this.fetchGithubUsername();
|
const githubUsername = await this.fetchGithubUsername();
|
||||||
if(githubUsername) {
|
if (githubUsername) {
|
||||||
return githubUsername;
|
return githubUsername;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ export default class OpenDBC {
|
||||||
async fetchGithubUsername() {
|
async fetchGithubUsername() {
|
||||||
try {
|
try {
|
||||||
const user = await this.github.getUser();
|
const user = await this.github.getUser();
|
||||||
if(user) {
|
if (user) {
|
||||||
const profile = await user.getProfile();
|
const profile = await user.getProfile();
|
||||||
if(profile) {
|
if (profile) {
|
||||||
return profile.data.login;
|
return profile.data.login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,31 +47,31 @@ export default class OpenDBC {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let repo;
|
let repo;
|
||||||
if(repoFullName === undefined) {
|
if (repoFullName === undefined) {
|
||||||
repo = this.sourceRepo;
|
repo = this.sourceRepo;
|
||||||
} else {
|
} else {
|
||||||
const [username, repoName] = repoFullName.split('/');
|
const [username, repoName] = repoFullName.split("/");
|
||||||
repo = this.github.getRepo(username, repoName);
|
repo = this.github.getRepo(username, repoName);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await repo.getContents('master', '');
|
const response = await repo.getContents("master", "");
|
||||||
|
|
||||||
return response.data.map((content) => content.path);
|
return response.data.map(content => content.path);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDbcContents(dbcPath, repoFullName) {
|
async getDbcContents(dbcPath, repoFullName) {
|
||||||
let repo;
|
let repo;
|
||||||
if(repoFullName === undefined) {
|
if (repoFullName === undefined) {
|
||||||
repo = this.sourceRepo;
|
repo = this.sourceRepo;
|
||||||
} else {
|
} else {
|
||||||
const [username, repoName] = repoFullName.split('/');
|
const [username, repoName] = repoFullName.split("/");
|
||||||
repo = this.github.getRepo(username, repoName);
|
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;
|
const rawContentsUrl = fileContents.data.download_url;
|
||||||
|
|
||||||
|
@ -81,72 +81,83 @@ export default class OpenDBC {
|
||||||
}
|
}
|
||||||
|
|
||||||
repoSourceIsOpenDbc(repoDetails) {
|
repoSourceIsOpenDbc(repoDetails) {
|
||||||
return repoDetails.source
|
return (
|
||||||
&& repoDetails.source.full_name === OPENDBC_SOURCE_REPO;
|
repoDetails.source && repoDetails.source.full_name === OPENDBC_SOURCE_REPO
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserOpenDbcFork() {
|
async getUserOpenDbcFork() {
|
||||||
const githubUsername = await this.getGithubUsername();
|
const githubUsername = await this.getGithubUsername();
|
||||||
if(!githubUsername) return null;
|
if (!githubUsername) return null;
|
||||||
|
|
||||||
const openDbcFork = this.github.getRepo(githubUsername, 'opendbc');
|
const openDbcFork = this.github.getRepo(githubUsername, "opendbc");
|
||||||
const repoDetailResp = await openDbcFork.getDetails();
|
const repoDetailResp = await openDbcFork.getDetails();
|
||||||
const repoDetails = repoDetailResp.data;
|
const repoDetails = repoDetailResp.data;
|
||||||
|
|
||||||
if(this.repoSourceIsOpenDbc(repoDetails)) {
|
if (this.repoSourceIsOpenDbc(repoDetails)) {
|
||||||
return repoDetails.full_name;
|
return repoDetails.full_name;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fork() {
|
async fork() {
|
||||||
const forkResponse = await this.sourceRepo.fork();
|
const forkResponse = await this.sourceRepo.fork();
|
||||||
if(forkResponse.status === 202) {
|
if (forkResponse.status === 202) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async commitFile(repoFullName, path, contents) {
|
async commitFile(repoFullName, path, contents) {
|
||||||
/*
|
/*
|
||||||
repo is of format username/reponame
|
repo is of format username/reponame
|
||||||
authenciated user must have write access to repo
|
authenciated user must have write access to repo
|
||||||
*/
|
*/
|
||||||
const [user, repoName] = repoFullName.split('/');
|
const [user, repoName] = repoFullName.split("/");
|
||||||
const repo = this.github.getRepo(user, repoName);
|
const repo = this.github.getRepo(user, repoName);
|
||||||
|
|
||||||
// get HEAD reference
|
// get HEAD reference
|
||||||
const refResp = await repo.getRef('heads/master');
|
const refResp = await repo.getRef("heads/master");
|
||||||
const ref = refResp.data;
|
const ref = refResp.data;
|
||||||
|
|
||||||
// get HEAD commit sha
|
// get HEAD commit sha
|
||||||
const headCommitResp = await repo.getCommit(ref.object.sha);
|
const headCommitResp = await repo.getCommit(ref.object.sha);
|
||||||
const headCommit = headCommitResp.data;
|
const headCommit = headCommitResp.data;
|
||||||
|
|
||||||
// get HEAD tree
|
// get HEAD tree
|
||||||
const headTreeResp = await repo.getTree(headCommit.tree.sha);
|
const headTreeResp = await repo.getTree(headCommit.tree.sha);
|
||||||
const headTree = headTreeResp.data;
|
const headTree = headTreeResp.data;
|
||||||
|
|
||||||
// create new tree
|
// create new tree
|
||||||
const tree = [{
|
const tree = [
|
||||||
mode: '100644',
|
{
|
||||||
path: path,
|
mode: "100644",
|
||||||
type: 'blob',
|
path: path,
|
||||||
content: contents,
|
type: "blob",
|
||||||
}];
|
content: contents
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const createTreeResp = await repo.createTree(tree, headTree.sha);
|
const createTreeResp = await repo.createTree(tree, headTree.sha);
|
||||||
const createdTree = createTreeResp.data;
|
const createdTree = createTreeResp.data;
|
||||||
|
|
||||||
// commit
|
// commit
|
||||||
const commitResp = await repo.commit(headCommit.sha, createdTree.sha, 'OpenDBC updates');
|
const commitResp = await repo.commit(
|
||||||
const commit = commitResp.data;
|
headCommit.sha,
|
||||||
|
createdTree.sha,
|
||||||
|
"OpenDBC updates"
|
||||||
|
);
|
||||||
|
const commit = commitResp.data;
|
||||||
|
|
||||||
// update HEAD
|
// update HEAD
|
||||||
const updateHeadResp = await repo.updateHead('heads/master', commit.sha, false);
|
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) {
|
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) {
|
export async function fetchCanPart(base, part) {
|
||||||
var urls = [ base+"/Log/"+part+"/can/t",
|
var urls = [
|
||||||
base+"/Log/"+part+"/can/src",
|
base + "/Log/" + part + "/can/t",
|
||||||
base+"/Log/"+part+"/can/address",
|
base + "/Log/" + part + "/can/src",
|
||||||
base+"/Log/"+part+"/can/data"];
|
base + "/Log/" + part + "/can/address",
|
||||||
|
base + "/Log/" + part + "/can/data"
|
||||||
|
];
|
||||||
|
|
||||||
var messages = {};
|
var messages = {};
|
||||||
let canData = null;
|
let canData = null;
|
||||||
try {
|
try {
|
||||||
canData = await Promise.all(urls.map(NumpyLoader.promise));
|
canData = await Promise.all(urls.map(NumpyLoader.promise));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('this is a 404 workaround that is hacky', e)
|
console.log("this is a 404 workaround that is hacky", e);
|
||||||
return {times: [],
|
return {
|
||||||
sources: [],
|
times: [],
|
||||||
addresses: [],
|
sources: [],
|
||||||
datas: []}
|
addresses: [],
|
||||||
}
|
datas: []
|
||||||
return {times: canData[0].data,
|
};
|
||||||
sources: canData[1].data,
|
}
|
||||||
addresses: canData[2].data,
|
return {
|
||||||
datas: canData[3].data};
|
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() {
|
function getCommaAccessToken() {
|
||||||
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
|
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAuthenticated() {
|
function isAuthenticated() {
|
||||||
return getCommaAccessToken() !== undefined;
|
return getCommaAccessToken() !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function authUrl() {
|
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 { GITHUB_CLIENT_ID, GITHUB_REDIRECT_URL } from "../config";
|
||||||
import {objToQuery} from '../utils/url';
|
import { objToQuery } from "../utils/url";
|
||||||
|
|
||||||
export function authorizeUrl(route) {
|
export function authorizeUrl(route) {
|
||||||
const params = {client_id: GITHUB_CLIENT_ID,
|
const params = {
|
||||||
redirect_uri: GITHUB_REDIRECT_URL,
|
client_id: GITHUB_CLIENT_ID,
|
||||||
scope: 'user:email public_repo',
|
redirect_uri: GITHUB_REDIRECT_URL,
|
||||||
state: JSON.stringify({route})};
|
scope: "user:email public_repo",
|
||||||
|
state: JSON.stringify({ route })
|
||||||
|
};
|
||||||
|
|
||||||
return `http://github.com/login/oauth/authorize?${objToQuery(params)}`;
|
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) {
|
export function fetchPersistedDbc(routeName) {
|
||||||
const maybeDbc = window.localStorage.getItem(routeName);
|
const maybeDbc = window.localStorage.getItem(routeName);
|
||||||
if(maybeDbc !== null) {
|
if (maybeDbc !== null) {
|
||||||
const {dbcFilename, dbcText} = JSON.parse(maybeDbc);
|
const { dbcFilename, dbcText } = JSON.parse(maybeDbc);
|
||||||
const dbc = new DBC(dbcText);
|
const dbc = new DBC(dbcText);
|
||||||
|
|
||||||
return {dbc, dbcText, dbcFilename};
|
return { dbc, dbcText, dbcFilename };
|
||||||
} else return null;
|
} else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function persistDbc(routeName, {dbcFilename, dbc}) {
|
export function persistDbc(routeName, { dbcFilename, dbc }) {
|
||||||
const dbcJson = JSON.stringify({dbcFilename,
|
const dbcJson = JSON.stringify({
|
||||||
dbcText: dbc.text()});
|
dbcFilename,
|
||||||
|
dbcText: dbc.text()
|
||||||
|
});
|
||||||
window.localStorage.setItem(routeName, dbcJson);
|
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() {
|
export function fetchPersistedGithubAuthToken() {
|
||||||
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
|
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unpersistGithubAuthToken() {
|
export function unpersistGithubAuthToken() {
|
||||||
|
@ -26,5 +28,5 @@ export function unpersistGithubAuthToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function persistGithubAuthToken(token) {
|
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 {
|
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.readLoop = this.readLoop.bind(this);
|
||||||
this.panda = new Panda();
|
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
|
||||||
this.isReading = false;
|
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
|
||||||
this.onMessagesReceived = () => {};
|
}
|
||||||
this.callbackQueue = [];
|
|
||||||
this.callbackQueueTimer = null;
|
|
||||||
|
|
||||||
this.readLoop = this.readLoop.bind(this);
|
connect() {
|
||||||
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
|
return this.panda.connect();
|
||||||
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
|
}
|
||||||
|
|
||||||
|
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() {
|
this.callbackQueueTimer = window.requestAnimationFrame(
|
||||||
return this.panda.connect();
|
this.flushCallbackQueue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
readLoop() {
|
||||||
|
if (!this.isReading) {
|
||||||
|
this.isReading = true;
|
||||||
|
// this.flushCallbackQueueTimer = wi
|
||||||
|
this.callbackQueueTimer = window.requestAnimationFrame(
|
||||||
|
this.flushCallbackQueue,
|
||||||
|
30
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnMessagesReceivedCallback(callback) {
|
this.panda.canRecv().then(
|
||||||
this.onMessagesReceived = callback;
|
messages => {
|
||||||
}
|
if (this.isReading && messages.canMessages.length > 0) {
|
||||||
|
this.callbackQueue.push(messages);
|
||||||
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.readLoop();
|
||||||
this.panda.canRecv().then(messages => {
|
},
|
||||||
if(this.isReading && messages.canMessages.length > 0) {
|
error => {
|
||||||
this.callbackQueue.push(messages);
|
if (this.isReading) {
|
||||||
}
|
console.log("canRecv error", error);
|
||||||
this.readLoop();
|
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';
|
import CloudLog from "../logging/CloudLog";
|
||||||
require('core-js/fn/string/pad-end');
|
require("core-js/fn/string/pad-end");
|
||||||
|
|
||||||
const PANDA_VENDOR_ID = 0xbbaa;
|
const PANDA_VENDOR_ID = 0xbbaa;
|
||||||
const PANDA_PRODUCT_ID = 0xddcc;
|
const PANDA_PRODUCT_ID = 0xddcc;
|
||||||
|
@ -7,73 +7,93 @@ const PANDA_PRODUCT_ID = 0xddcc;
|
||||||
const BUFFER_SIZE = 0x10 * 256;
|
const BUFFER_SIZE = 0x10 * 256;
|
||||||
|
|
||||||
export default class Panda {
|
export default class Panda {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.device = null;
|
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() {
|
return messages;
|
||||||
// Must be called via a mouse click handler, per Chrome restrictions.
|
}
|
||||||
return navigator.usb.requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] })
|
|
||||||
.then(device => {
|
async mockCanRecv() {
|
||||||
this.device = device;
|
const promise = new Promise(resolve =>
|
||||||
return device.open();
|
setTimeout(
|
||||||
})
|
() =>
|
||||||
.then(() => this.device.selectConfiguration(1))
|
resolve({
|
||||||
.then(() => this.device.claimInterface(0));
|
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() {
|
return {
|
||||||
const controlParams = { requestType: 'vendor',
|
time: receiptTime,
|
||||||
recipient: 'device',
|
canMessages: this.parseCanBuffer(result.data.buffer)
|
||||||
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)};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from "js-cookie";
|
||||||
import Moment from 'moment';
|
import Moment from "moment";
|
||||||
import CommaAuth from './comma-auth';
|
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) {
|
function momentizeTimes(routes) {
|
||||||
for(let routeName in routes) {
|
for (let routeName in routes) {
|
||||||
routes[routeName].start_time = Moment(routes[routeName].start_time);
|
routes[routeName].start_time = Moment(routes[routeName].start_time);
|
||||||
routes[routeName].end_time = Moment(routes[routeName].end_time);
|
routes[routeName].end_time = Moment(routes[routeName].end_time);
|
||||||
}
|
}
|
||||||
|
@ -13,35 +13,34 @@ function momentizeTimes(routes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchRoutes(dongleId) {
|
export async function fetchRoutes(dongleId) {
|
||||||
// will throw errors from fetch() on HTTP failure
|
// will throw errors from fetch() on HTTP failure
|
||||||
|
|
||||||
if(dongleId === undefined) {
|
if (dongleId === undefined) {
|
||||||
dongleId = 'me';
|
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();
|
return {};
|
||||||
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 {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cameraPath(routeUrl, frame) {
|
export function cameraPath(routeUrl, frame) {
|
||||||
return `${routeUrl}/sec${frame}.jpg`
|
return `${routeUrl}/sec${frame}.jpg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseRouteName(name) {
|
export function parseRouteName(name) {
|
||||||
const startTime = Moment(name, "YYYY-MM-DD--H-m-s");
|
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 SocketIO from "socket.io-client";
|
||||||
import {UNLOGGER_HOST} from '../config';
|
import { UNLOGGER_HOST } from "../config";
|
||||||
|
|
||||||
export default class UnloggerClient {
|
export default class UnloggerClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.socket = SocketIO(UNLOGGER_HOST);
|
this.socket = SocketIO(UNLOGGER_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
seek(dongleId, baseTime, seekSeconds) {
|
seek(dongleId, baseTime, seekSeconds) {
|
||||||
this.socket.emit('seek', dongleId, baseTime, seekSeconds);
|
this.socket.emit("seek", dongleId, baseTime, seekSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,18 @@ function videoUrl(dongleId, hashedRouteName) {
|
||||||
function videoUrlForRouteUrl(routeUrlString) {
|
function videoUrlForRouteUrl(routeUrlString) {
|
||||||
const url = new URL(routeUrlString);
|
const url = new URL(routeUrlString);
|
||||||
|
|
||||||
const pathParts = url.pathname.split('/');
|
const pathParts = url.pathname.split("/");
|
||||||
|
|
||||||
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(pathParts.length - 2);
|
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(
|
||||||
let dongleId = dongleIdPrefixed
|
pathParts.length - 2
|
||||||
if(dongleIdPrefixed.indexOf('comma-') === 0) {
|
);
|
||||||
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split('comma-');
|
let dongleId = dongleIdPrefixed;
|
||||||
|
if (dongleIdPrefixed.indexOf("comma-") === 0) {
|
||||||
|
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split("comma-");
|
||||||
dongleId = dongleIdNoPrefix;
|
dongleId = dongleIdNoPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoUrl(dongleId, hashedRouteName);
|
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(`
|
const CivicDbc = new DBC(`
|
||||||
VERSION ""
|
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" ;
|
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 React, { Component } from "react";
|
||||||
import Measure from 'react-measure';
|
import Measure from "react-measure";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
|
|
||||||
import Signal from '../models/can/signal';
|
import Signal from "../models/can/signal";
|
||||||
import CanPlot from '../vega/CanPlot';
|
import CanPlot from "../vega/CanPlot";
|
||||||
|
|
||||||
const DefaultPlotInnerStyle = {
|
const DefaultPlotInnerStyle = {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class CanGraph extends Component {
|
export default class CanGraph extends Component {
|
||||||
static emptyTable = [];
|
static emptyTable = [];
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
data: PropTypes.object,
|
data: PropTypes.object,
|
||||||
messages: PropTypes.object,
|
messages: PropTypes.object,
|
||||||
messageId: PropTypes.string,
|
messageId: PropTypes.string,
|
||||||
messageName: PropTypes.string,
|
messageName: PropTypes.string,
|
||||||
signalSpec: PropTypes.instanceOf(Signal),
|
signalSpec: PropTypes.instanceOf(Signal),
|
||||||
segment: PropTypes.array,
|
segment: PropTypes.array,
|
||||||
unplot: PropTypes.func,
|
unplot: PropTypes.func,
|
||||||
onRelativeTimeClick: PropTypes.func,
|
onRelativeTimeClick: PropTypes.func,
|
||||||
currentTime: PropTypes.number,
|
currentTime: PropTypes.number,
|
||||||
onSegmentChanged: PropTypes.func,
|
onSegmentChanged: PropTypes.func,
|
||||||
onDragStart: PropTypes.func,
|
onDragStart: PropTypes.func,
|
||||||
onDragEnd: PropTypes.func,
|
onDragEnd: PropTypes.func,
|
||||||
container: PropTypes.node,
|
container: PropTypes.node,
|
||||||
dragPos: PropTypes.object,
|
dragPos: PropTypes.object,
|
||||||
canReceiveGraphDrop: PropTypes.bool,
|
canReceiveGraphDrop: PropTypes.bool,
|
||||||
onGraphRefAvailable: PropTypes.func,
|
onGraphRefAvailable: PropTypes.func,
|
||||||
plottedSignals: PropTypes.array,
|
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) {
|
segmentIsNew(newSegment) {
|
||||||
super(props);
|
return (
|
||||||
|
newSegment.length !== this.props.segment.length ||
|
||||||
|
!newSegment.every((val, idx) => this.props.segment[idx] === val)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
dataChanged(prevProps, nextProps) {
|
||||||
plotInnerStyle: null,
|
return (
|
||||||
shiftX: 0,
|
nextProps.data.series.length !== prevProps.data.series.length ||
|
||||||
shiftY: 0,
|
!prevProps.signalSpec.equals(nextProps.signalSpec) ||
|
||||||
bounds: null,
|
nextProps.data.updated !== this.props.data.updated
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
segmentIsNew(newSegment) {
|
visualChanged(prevProps, nextProps) {
|
||||||
return newSegment.length !== this.props.segment.length
|
return (
|
||||||
|| !(newSegment.every((val, idx) => this.props.segment[idx] === val));
|
prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop ||
|
||||||
}
|
JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
dataChanged(prevProps, nextProps) {
|
onPlotResize({ bounds }) {
|
||||||
return nextProps.data.series.length !== prevProps.data.series.length
|
this.setState({ bounds });
|
||||||
|| !prevProps.signalSpec.equals(nextProps.signalSpec)
|
|
||||||
|| nextProps.data.updated !== this.props.data.updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
visualChanged(prevProps, nextProps) {
|
this.view.run();
|
||||||
return prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop
|
this.view.signal("width", bounds.width);
|
||||||
|| JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos);
|
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}) {
|
if (!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
|
||||||
this.setState({bounds});
|
this.view.signal("videoTime", nextProps.currentTime);
|
||||||
|
segmentChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segmentChanged) {
|
||||||
this.view.run();
|
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) {
|
const dataChanged = this.dataChanged(this.props, nextProps);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
|
return (
|
||||||
this.view.signal('videoTime', nextProps.currentTime);
|
dataChanged ||
|
||||||
segmentChanged = true;
|
JSON.stringify(this.state) !== JSON.stringify(nextState) ||
|
||||||
}
|
this.visualChanged(this.props, nextProps)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(segmentChanged) {
|
insertData() {
|
||||||
this.view.run();
|
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
|
componentWillReceiveProps(nextProps) {
|
||||||
|| JSON.stringify(this.state) !== JSON.stringify(nextState)
|
if (
|
||||||
|| this.visualChanged(this.props, nextProps);
|
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.insertData();
|
||||||
this.view.remove('table', () => true).run();
|
}
|
||||||
this.view.insert('table', this.props.data.series).run();
|
|
||||||
|
onSignalClickTime(signal, clickTime) {
|
||||||
|
if (clickTime !== undefined) {
|
||||||
|
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSignalSegment(signal, segment) {
|
||||||
|
if (!Array.isArray(segment)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
this.props.onSegmentChanged(this.props.messageId, segment);
|
||||||
if(this.dataChanged(prevProps, this.props)) {
|
if (this.view) {
|
||||||
this.insertData();
|
const state = this.view.getState();
|
||||||
}
|
state.subcontext[0].signals.brush = 0;
|
||||||
|
this.view = this.view.setState(state);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
plotInnerStyleFromMouseEvent(e) {
|
||||||
if(nextProps.dragPos && JSON.stringify(nextProps.dragPos) !== JSON.stringify(this.props.dragPos)) {
|
const { shiftX, shiftY } = this.state;
|
||||||
this.updateStyleFromDragPos(nextProps.dragPos);
|
const plotInnerStyle = { ...DefaultPlotInnerStyle };
|
||||||
} else if(!nextProps.dragPos && this.state.plotInnerStyle !== null) {
|
const rect = this.props.container.getBoundingClientRect();
|
||||||
this.setState({plotInnerStyle: null});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStyleFromDragPos({left, top}) {
|
const x = e.clientX - rect.left - shiftX;
|
||||||
const plotInnerStyle = this.state.plotInnerStyle || {};
|
const y = e.clientY - rect.top - shiftY;
|
||||||
plotInnerStyle.left = left;
|
plotInnerStyle.left = x;
|
||||||
plotInnerStyle.top = top;
|
plotInnerStyle.top = y;
|
||||||
this.setState({plotInnerStyle});
|
return plotInnerStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewView(view) {
|
onDragAnchorMouseDown(e) {
|
||||||
this.view = view;
|
e.persist();
|
||||||
if(this.state.bounds) {
|
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
|
||||||
this.onPlotResize({bounds: this.state.bounds});
|
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
|
||||||
}
|
this.setState({ shiftX, shiftY }, () => {
|
||||||
if(this.props.segment.length > 0) {
|
this.setState({ plotInnerStyle: this.plotInnerStyleFromMouseEvent(e) });
|
||||||
view.signal('segment', this.props.segment);
|
});
|
||||||
}
|
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) {
|
onDragStart(e) {
|
||||||
if(clickTime !== undefined) {
|
e.preventDefault();
|
||||||
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onSignalSegment(signal, segment) {
|
render() {
|
||||||
if(!Array.isArray(segment)) {
|
const { plotInnerStyle } = this.state;
|
||||||
return;
|
const canReceiveDropClass = this.props.canReceiveGraphDrop
|
||||||
}
|
? "is-droppable"
|
||||||
|
: null;
|
||||||
|
|
||||||
this.props.onSegmentChanged(this.props.messageId, segment);
|
return (
|
||||||
if(this.view) {
|
<div
|
||||||
const state = this.view.getState();
|
className="cabana-explorer-visuals-plot"
|
||||||
state.subcontext[0].signals.brush = 0;
|
ref={this.props.onGraphRefAvailable}
|
||||||
this.view = this.view.setState(state);
|
>
|
||||||
}
|
<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) {
|
return (
|
||||||
const {shiftX, shiftY} = this.state;
|
<div
|
||||||
const plotInnerStyle = {...DefaultPlotInnerStyle};
|
className="cabana-explorer-visuals-plot-header"
|
||||||
const rect = this.props.container.getBoundingClientRect();
|
key={messageId + "_" + signal.uid}
|
||||||
|
>
|
||||||
const x = e.clientX - rect.left - shiftX;
|
<div className="cabana-explorer-visuals-plot-header-toggle">
|
||||||
const y = e.clientY - rect.top - shiftY;
|
<button
|
||||||
plotInnerStyle.left = x;
|
className="button--tiny"
|
||||||
plotInnerStyle.top = y;
|
onClick={() => this.props.unplot(messageId, signalUid)}
|
||||||
return plotInnerStyle;
|
>
|
||||||
}
|
<span>Hide Plot</span>
|
||||||
|
</button>
|
||||||
onDragAnchorMouseDown(e) {
|
</div>
|
||||||
e.persist();
|
<div className="cabana-explorer-visuals-plot-header-copy">
|
||||||
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
|
<div className="cabana-explorer-visuals-plot-message">
|
||||||
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
|
<span>{messageName}</span>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
{this.props.plottedSignals.map(({ messageId, signalUid, messageName }) => {
|
<div className="cabana-explorer-visuals-plot-signal">
|
||||||
const signal = Object.values(this.props.messages[messageId].frame.signals).find((s) => s.uid === signalUid);
|
<div
|
||||||
const { colors } = signal;
|
className="cabana-explorer-visuals-plot-signal-color"
|
||||||
|
style={{ background: `rgb(${colors}` }}
|
||||||
return (
|
/>
|
||||||
<div className='cabana-explorer-visuals-plot-header'
|
<strong>{signal.name}</strong>
|
||||||
key={messageId + '_' + signal.uid}>
|
</div>
|
||||||
<div className='cabana-explorer-visuals-plot-header-toggle'>
|
</div>
|
||||||
<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>
|
</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 React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import ReactList from 'react-list';
|
import ReactList from "react-list";
|
||||||
|
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
|
|
||||||
export default class CanLog extends Component {
|
export default class CanLog extends Component {
|
||||||
static ITEMS_PER_PAGE = 50;
|
static ITEMS_PER_PAGE = 50;
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
plottedSignals: PropTypes.array,
|
plottedSignals: PropTypes.array,
|
||||||
segmentIndices: PropTypes.array,
|
segmentIndices: PropTypes.array,
|
||||||
onSignalUnplotPressed: PropTypes.func,
|
onSignalUnplotPressed: PropTypes.func,
|
||||||
onSignalPlotPressed: PropTypes.func,
|
onSignalPlotPressed: PropTypes.func,
|
||||||
message: PropTypes.object,
|
message: PropTypes.object,
|
||||||
messageIndex: PropTypes.number,
|
messageIndex: PropTypes.number,
|
||||||
onMessageExpanded: PropTypes.func,
|
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) {
|
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
|
||||||
super(props);
|
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
|
||||||
// only want to display up to length elements at a time
|
this.renderLogListItem = this.renderLogListItem.bind(this);
|
||||||
// offset, length
|
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 = {
|
componentWillReceiveProps(nextProps) {
|
||||||
length: 0,
|
if (nextProps.message && !this.props.message) {
|
||||||
expandedMessages: [],
|
this.addDisplayedMessages();
|
||||||
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) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
if(nextProps.message && !this.props.message) {
|
const curMessageLength = this.props.message
|
||||||
this.addDisplayedMessages();
|
? this.props.message.entries.length
|
||||||
}
|
: 0;
|
||||||
}
|
const nextMessageLength = nextProps.message
|
||||||
|
? nextProps.message.entries.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
const shouldUpdate =
|
||||||
const curMessageLength = this.props.message ? this.props.message.entries.length : 0;
|
this.props.message !== nextProps.message ||
|
||||||
const nextMessageLength = nextProps.message ? nextProps.message.entries.length : 0;
|
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
|
return shouldUpdate;
|
||||||
|| 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;
|
addDisplayedMessages() {
|
||||||
}
|
const { length } = this.state;
|
||||||
|
const newLength = length + CanLog.ITEMS_PER_PAGE;
|
||||||
|
|
||||||
addDisplayedMessages() {
|
this.setState({ length: newLength });
|
||||||
const {length} = this.state;
|
}
|
||||||
const newLength = length + CanLog.ITEMS_PER_PAGE;
|
|
||||||
|
|
||||||
this.setState({length: newLength});
|
expandMessage(msg, msgIdx) {
|
||||||
}
|
this.setState({
|
||||||
|
expandedMessages: this.state.expandedMessages.concat([msg.time])
|
||||||
|
});
|
||||||
|
this.props.onMessageExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
expandMessage(msg, msgIdx) {
|
collapseMessage(msg, msgIdx) {
|
||||||
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
|
this.setState({
|
||||||
this.props.onMessageExpanded();
|
expandedMessages: this.state.expandedMessages.filter(
|
||||||
}
|
expMsgTime => expMsgTime !== msg.time
|
||||||
|
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
toggleExpandPacketSignals(msgEntry) {
|
||||||
const { message } = this.props;
|
if (!this.props.message.frame) {
|
||||||
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
const msgIsExpanded =
|
||||||
|
this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
|
||||||
|
|
||||||
renderLogListItem(index, key) {
|
const msgHasSignals =
|
||||||
let offset = this.props.messageIndex;
|
Object.keys(this.props.message.frame.signals).length > 0;
|
||||||
if(offset === 0 && this.props.segmentIndices.length === 2) {
|
if (msgIsExpanded && msgHasSignals) {
|
||||||
offset = this.props.segmentIndices[0];
|
this.setState({
|
||||||
}
|
expandedMessages: this.state.expandedMessages.filter(
|
||||||
if((offset + index) < this.props.message.entries.length) {
|
expMsgTime => expMsgTime !== msgEntry.time
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
} else if (msgHasSignals) {
|
||||||
|
this.setState({
|
||||||
|
expandedMessages: this.state.expandedMessages.concat([msgEntry.time])
|
||||||
|
});
|
||||||
|
this.props.onMessageExpanded();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
listLength() {
|
renderLogListItemSignals(msgEntry) {
|
||||||
const {segmentIndices, messageIndex} = this.props;
|
const { message } = this.props;
|
||||||
if(messageIndex > 0) {
|
return (
|
||||||
return this.props.message.entries.length - messageIndex;
|
<div className="signals-log-list-signals">
|
||||||
} else if(segmentIndices.length === 2) {
|
{Object.entries(msgEntry.signals).map(([name, value]) => {
|
||||||
return segmentIndices[1] - segmentIndices[0];
|
const signal = message.frame.signals[name];
|
||||||
} else if(this.props.message) {
|
if (signal === undefined) {
|
||||||
return this.props.message.entries.length;
|
// Signal removed?
|
||||||
} else {
|
return null;
|
||||||
// no message yet
|
}
|
||||||
return 0;
|
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";
|
||||||
onExpandAllChanged(e) {
|
return (
|
||||||
this.setState({allPacketsExpanded: e.target.checked});
|
<div key={name} className="signals-log-list-signal">
|
||||||
}
|
<div className="signals-log-list-signal-message">
|
||||||
|
<span>{name}</span>
|
||||||
toggleExpandAllPackets() {
|
</div>
|
||||||
this.setState({allPacketsExpanded: !this.state.allPacketsExpanded});
|
<div className="signals-log-list-signal-value">
|
||||||
}
|
<span>
|
||||||
|
(<strong>{this.signalValuePretty(signal, value)}</strong>{" "}
|
||||||
render() {
|
{unit})
|
||||||
let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All';
|
</span>
|
||||||
let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha';
|
</div>
|
||||||
return (
|
<div
|
||||||
<div className='cabana-explorer-signals-log'>
|
className="signals-log-list-signal-action"
|
||||||
<div className='cabana-explorer-signals-log-header'>
|
onClick={() => {
|
||||||
<strong>Message Packets</strong>
|
this.toggleSignalPlot(message.id, signal.uid, isPlotted);
|
||||||
<button className={cx('button--tiny', expandAllClass)}
|
}}
|
||||||
onClick={this.toggleExpandAllPackets}>
|
>
|
||||||
{expandAllText}
|
<button className={cx("button--tiny", plottedButtonClass)}>
|
||||||
</button>
|
<span>{plottedButtonText}</span>
|
||||||
</div>
|
</button>
|
||||||
<div className='cabana-explorer-signals-log-body'>
|
</div>
|
||||||
<ReactList
|
|
||||||
itemRenderer={this.renderLogListItem}
|
|
||||||
itemsRenderer={this.renderLogList}
|
|
||||||
length={this.listLength()}
|
|
||||||
pageSize={50}
|
|
||||||
updateWhenThisValueChanges={this.props.messageIndex}
|
|
||||||
type='variable' />
|
|
||||||
</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>
|
||||||
);
|
<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 React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export default class DbcUpload extends Component {
|
export default class DbcUpload extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onDbcLoaded: PropTypes.func
|
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) {
|
onTextChanged(e) {
|
||||||
const dbcText = e.target.value;
|
const dbcText = e.target.value;
|
||||||
this.setState({dbcText})
|
this.setState({ dbcText });
|
||||||
this.props.onDbcLoaded('from paste', dbcText);
|
this.props.onDbcLoaded("from paste", dbcText);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='cabana-dbc-upload-raw'>
|
<div className="cabana-dbc-upload-raw">
|
||||||
<div className='form-field'>
|
<div className="form-field">
|
||||||
<label htmlFor='raw_dbc_upload'>
|
<label htmlFor="raw_dbc_upload">
|
||||||
<span>Raw DBC File:</span>
|
<span>Raw DBC File:</span>
|
||||||
<sup>Paste your DBC text output within this box</sup>
|
<sup>Paste your DBC text output within this box</sup>
|
||||||
</label>
|
</label>
|
||||||
<textarea value={ this.state.dbcText }
|
<textarea
|
||||||
id='raw_dbc_upload'
|
value={this.state.dbcText}
|
||||||
className='t-mono'
|
id="raw_dbc_upload"
|
||||||
placeholder='PASTE DBC FILE HERE'
|
className="t-mono"
|
||||||
onChange={ this.onTextChanged } />
|
placeholder="PASTE DBC FILE HERE"
|
||||||
</div>
|
onChange={this.onTextChanged}
|
||||||
</div>
|
/>
|
||||||
);
|
</div>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,130 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import Modal from './Modals/baseModal';
|
import Modal from "./Modals/baseModal";
|
||||||
|
|
||||||
export default class EditMessageModal extends Component {
|
export default class EditMessageModal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
handleClose: PropTypes.func.isRequired,
|
handleClose: PropTypes.func.isRequired,
|
||||||
handleSave: PropTypes.func.isRequired,
|
handleSave: PropTypes.func.isRequired,
|
||||||
message: PropTypes.object.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) {
|
handleSave() {
|
||||||
super(props);
|
this.props.handleSave(this.state.messageFrame);
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
addTransmitter() {
|
||||||
messageFrame: props.message.frame.copy()
|
const { messageFrame } = this.state;
|
||||||
}
|
messageFrame.addTransmitter();
|
||||||
this.handleSave = this.handleSave.bind(this);
|
this.setState({ messageFrame });
|
||||||
this.editTransmitter = this.editTransmitter.bind(this);
|
}
|
||||||
this.addTransmitter = this.addTransmitter.bind(this);
|
|
||||||
this.renderActions = this.renderActions.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSave() {
|
editTransmitter(transmitter) {
|
||||||
this.props.handleSave(this.state.messageFrame);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTransmitter() {
|
renderActions() {
|
||||||
const {messageFrame} = this.state;
|
return (
|
||||||
messageFrame.addTransmitter();
|
<div>
|
||||||
this.setState({messageFrame});
|
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||||
}
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button className="button--primary" onClick={this.handleSave}>
|
||||||
|
Save Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
editTransmitter(transmitter) {
|
render() {
|
||||||
return;
|
return (
|
||||||
}
|
<Modal
|
||||||
|
title={`Edit Message: (${this.props.message.id})`}
|
||||||
renderActions() {
|
subtitle="Make changes and update defaults of this message"
|
||||||
return (
|
handleClose={this.props.handleClose}
|
||||||
<div>
|
handleSave={this.handleSave}
|
||||||
<button
|
actions={this.renderActions()}
|
||||||
className='button--inverted'
|
>
|
||||||
onClick={ this.props.handleClose }>Cancel</button>
|
<div className="form-field">
|
||||||
<button
|
<label htmlFor="message_name">
|
||||||
className='button--primary'
|
<span>Name</span>
|
||||||
onClick={ this.handleSave }>Save Message</button>
|
<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>
|
||||||
)
|
<div className="form-field">
|
||||||
}
|
<label htmlFor="message_size">
|
||||||
|
<span>Size</span>
|
||||||
render() {
|
<sup>Add a size parameter to this message</sup>
|
||||||
return (
|
</label>
|
||||||
<Modal
|
<input
|
||||||
title={ `Edit Message: (${ this.props.message.id })`}
|
type="number"
|
||||||
subtitle='Make changes and update defaults of this message'
|
id="message_size"
|
||||||
handleClose={ this.props.handleClose }
|
value={this.state.messageFrame.size}
|
||||||
handleSave={ this.handleSave }
|
onChange={e => {
|
||||||
actions={ this.renderActions() }>
|
const { messageFrame } = this.state;
|
||||||
<div className='form-field'>
|
if (e.target.value > 8) {
|
||||||
<label htmlFor='message_name'>
|
return;
|
||||||
<span>Name</span>
|
}
|
||||||
<sup>Customize the name of this message</sup>
|
messageFrame.size = parseInt(e.target.value, 10);
|
||||||
</label>
|
this.setState({ messageFrame });
|
||||||
<input
|
}}
|
||||||
type='text'
|
/>
|
||||||
id='message_name'
|
</div>
|
||||||
value={ this.state.messageFrame.name }
|
<div className="form-field u-hidden">
|
||||||
onChange={ (e) => {
|
<label htmlFor="message_transmitters">
|
||||||
const { messageFrame } = this.state;
|
<span>Transmitters</span>
|
||||||
messageFrame.name = e.target.value;
|
<sup>
|
||||||
this.setState({ messageFrame });
|
Add the physical ECU units that this message is coming from.
|
||||||
}} />
|
</sup>
|
||||||
</div>
|
</label>
|
||||||
<div className='form-field'>
|
<div className="form-field-inset">
|
||||||
<label htmlFor='message_size'>
|
<ul className="form-field-inset-list">
|
||||||
<span>Size</span>
|
{this.state.messageFrame.transmitters.map(transmitter => {
|
||||||
<sup>Add a size parameter to this message</sup>
|
return (
|
||||||
</label>
|
<li className="form-field-inset-list-item" key={transmitter}>
|
||||||
<input
|
<div className="form-field-inset-list-item-title">
|
||||||
type='number'
|
<span>{transmitter}</span>
|
||||||
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>
|
</div>
|
||||||
</div>
|
<div className="form-field-inset-list-item-action">
|
||||||
</Modal>
|
<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 React, { Component } from "react";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import OpenDbc from '../api/OpenDbc';
|
import OpenDbc from "../api/OpenDbc";
|
||||||
|
|
||||||
export default class GithubDbcList extends Component {
|
export default class GithubDbcList extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onDbcLoaded: PropTypes.func.isRequired,
|
onDbcLoaded: PropTypes.func.isRequired,
|
||||||
repo: PropTypes.string.isRequired,
|
repo: PropTypes.string.isRequired,
|
||||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
|
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
paths: [],
|
||||||
|
selectedPath: null,
|
||||||
|
pathQuery: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props){
|
this.updatePathQuery = this.updatePathQuery.bind(this);
|
||||||
super(props);
|
}
|
||||||
|
|
||||||
this.state = {
|
componentWillReceiveProps(nextProps) {
|
||||||
paths: [],
|
if (nextProps.repo !== this.props.repo) {
|
||||||
selectedPath: null,
|
this.props.openDbcClient.list(nextProps.repo).then(paths => {
|
||||||
pathQuery: ''
|
this.setState({ paths, selectedPath: null });
|
||||||
};
|
});
|
||||||
|
|
||||||
this.updatePathQuery = this.updatePathQuery.bind(this);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillMount() {
|
||||||
if(nextProps.repo !== this.props.repo) {
|
this.props.openDbcClient.list(this.props.repo).then(paths => {
|
||||||
this.props.openDbcClient.list(nextProps.repo).then((paths) => {
|
paths = paths.filter(path => path.indexOf(".dbc") !== -1);
|
||||||
this.setState({paths, selectedPath: null})
|
this.setState({ paths });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
updatePathQuery(e) {
|
||||||
this.props.openDbcClient.list(this.props.repo).then((paths) => {
|
this.setState({
|
||||||
paths = paths.filter((path) => path.indexOf(".dbc") !== -1);
|
pathQuery: e.target.value
|
||||||
this.setState({paths});
|
});
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updatePathQuery(e) {
|
selectPath(path) {
|
||||||
this.setState({
|
this.setState({ selectedPath: path });
|
||||||
pathQuery: e.target.value,
|
this.props.openDbcClient
|
||||||
})
|
.getDbcContents(path, this.props.repo)
|
||||||
}
|
.then(dbcContents => {
|
||||||
|
this.props.onDbcLoaded(path, dbcContents);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
selectPath(path) {
|
render() {
|
||||||
this.setState({selectedPath: path})
|
return (
|
||||||
this.props.openDbcClient.getDbcContents(path, this.props.repo).then((dbcContents) => {
|
<div className="cabana-dbc-list">
|
||||||
this.props.onDbcLoaded(path, dbcContents);
|
<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>
|
||||||
render() {
|
</a>
|
||||||
return (
|
<div className="form-field form-field--small">
|
||||||
<div className='cabana-dbc-list'>
|
<input
|
||||||
<div className='cabana-dbc-list-header'>
|
type="text"
|
||||||
<a href={ `https://github.com/${ this.props.repo }` }
|
placeholder="Search DBC Files"
|
||||||
target='_blank'>
|
onChange={this.updatePathQuery}
|
||||||
<i className='fa fa-github'></i>
|
/>
|
||||||
<span>{ this.props.repo }</span>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
<div className='form-field form-field--small'>
|
<div className="cabana-dbc-list-files">
|
||||||
<input type='text'
|
{this.state.paths
|
||||||
placeholder='Search DBC Files'
|
.filter(
|
||||||
onChange={ this.updatePathQuery }/>
|
p =>
|
||||||
</div>
|
(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 className='cabana-dbc-list-files'>
|
);
|
||||||
{ this.state.paths.filter(p => this.state.pathQuery === '' | p.includes(this.state.pathQuery))
|
})}
|
||||||
.map((path) => {
|
</div>
|
||||||
return (
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import Hls from 'hls.js/lib';
|
import Hls from "hls.js/lib";
|
||||||
|
|
||||||
export default class HLS extends Component {
|
export default class HLS extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -19,13 +19,16 @@ export default class HLS extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if( (nextProps.shouldRestart || nextProps.startTime !== this.props.startTime)
|
if (
|
||||||
&& isFinite(nextProps.startTime)) {
|
(nextProps.shouldRestart ||
|
||||||
|
nextProps.startTime !== this.props.startTime) &&
|
||||||
|
isFinite(nextProps.startTime)
|
||||||
|
) {
|
||||||
this.videoElement.currentTime = nextProps.startTime;
|
this.videoElement.currentTime = nextProps.startTime;
|
||||||
this.props.onRestart();
|
this.props.onRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nextProps.playing) {
|
if (nextProps.playing) {
|
||||||
this.videoElement.play();
|
this.videoElement.play();
|
||||||
} else {
|
} else {
|
||||||
this.videoElement.pause();
|
this.videoElement.pause();
|
||||||
|
@ -37,20 +40,20 @@ export default class HLS extends Component {
|
||||||
this.player.loadSource(this.props.source);
|
this.player.loadSource(this.props.source);
|
||||||
this.player.attachMedia(this.videoElement);
|
this.player.attachMedia(this.videoElement);
|
||||||
// these events fire when video is playing
|
// these events fire when video is playing
|
||||||
this.videoElement.addEventListener('waiting', this.props.onLoadStart);
|
this.videoElement.addEventListener("waiting", this.props.onLoadStart);
|
||||||
this.videoElement.addEventListener('playing', this.props.onLoadEnd);
|
this.videoElement.addEventListener("playing", this.props.onLoadEnd);
|
||||||
|
|
||||||
// these events fire when video is paused & seeked
|
// these events fire when video is paused & seeked
|
||||||
this.videoElement.addEventListener('seeking', () => {
|
this.videoElement.addEventListener("seeking", () => {
|
||||||
if(!this.props.playing) {
|
if (!this.props.playing) {
|
||||||
this.props.onLoadStart();
|
this.props.onLoadStart();
|
||||||
this.props.onPlaySeek(this.videoElement.currentTime);
|
this.props.onPlaySeek(this.videoElement.currentTime);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let shouldInitVideoTime = true;
|
let shouldInitVideoTime = true;
|
||||||
this.videoElement.addEventListener('seeked', () => {
|
this.videoElement.addEventListener("seeked", () => {
|
||||||
if(!this.props.playing) {
|
if (!this.props.playing) {
|
||||||
if(shouldInitVideoTime) {
|
if (shouldInitVideoTime) {
|
||||||
this.videoElement.currentTime = this.props.startTime;
|
this.videoElement.currentTime = this.props.startTime;
|
||||||
shouldInitVideoTime = false;
|
shouldInitVideoTime = false;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +62,7 @@ export default class HLS extends Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onVideoElementAvailable(this.videoElement);
|
this.props.onVideoElementAvailable(this.videoElement);
|
||||||
if(this.props.playing) {
|
if (this.props.playing) {
|
||||||
this.videoElement.play();
|
this.videoElement.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,9 +70,14 @@ export default class HLS extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='cabana-explorer-visuals-camera-wrapper'
|
className="cabana-explorer-visuals-camera-wrapper"
|
||||||
onClick={this.props.onClick}>
|
onClick={this.props.onClick}
|
||||||
<video ref={ (video) => { this.videoElement = video; } } />
|
>
|
||||||
|
<video
|
||||||
|
ref={video => {
|
||||||
|
this.videoElement = video;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,127 +1,129 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
|
|
||||||
import DBC from '../models/can/dbc';
|
import DBC from "../models/can/dbc";
|
||||||
import OpenDbc from '../api/OpenDbc';
|
import OpenDbc from "../api/OpenDbc";
|
||||||
import Modal from './Modals/baseModal';
|
import Modal from "./Modals/baseModal";
|
||||||
import GithubDbcList from './GithubDbcList';
|
import GithubDbcList from "./GithubDbcList";
|
||||||
import DbcUpload from './DbcUpload';
|
import DbcUpload from "./DbcUpload";
|
||||||
|
|
||||||
export default class LoadDbcModal extends Component {
|
export default class LoadDbcModal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
handleClose: PropTypes.func.isRequired,
|
handleClose: PropTypes.func.isRequired,
|
||||||
onDbcSelected: PropTypes.func.isRequired,
|
onDbcSelected: PropTypes.func.isRequired,
|
||||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||||
loginWithGithub: PropTypes.element.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) {
|
this.onDbcLoaded = this.onDbcLoaded.bind(this);
|
||||||
super(props);
|
this.handleSave = this.handleSave.bind(this);
|
||||||
this.state = {
|
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||||
tab: 'OpenDBC',
|
this.renderTabContent = this.renderTabContent.bind(this);
|
||||||
tabs: ['OpenDBC', 'GitHub', 'Upload'],
|
this.renderActions = this.renderActions.bind(this);
|
||||||
dbc: null,
|
}
|
||||||
dbcSource: null,
|
|
||||||
userOpenDbcRepo: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onDbcLoaded = this.onDbcLoaded.bind(this);
|
componentWillMount() {
|
||||||
this.handleSave = this.handleSave.bind(this);
|
this.props.openDbcClient.getUserOpenDbcFork().then(userOpenDbcRepo => {
|
||||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
this.setState({ userOpenDbcRepo });
|
||||||
this.renderTabContent = this.renderTabContent.bind(this);
|
});
|
||||||
this.renderActions = this.renderActions.bind(this);
|
}
|
||||||
|
|
||||||
|
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() {
|
renderTabNavigation() {
|
||||||
this.props.openDbcClient.getUserOpenDbcFork().then((userOpenDbcRepo) => {
|
return (
|
||||||
this.setState({userOpenDbcRepo});
|
<div className="cabana-tabs-navigation">
|
||||||
});
|
{this.state.tabs.map(tab => {
|
||||||
}
|
|
||||||
|
|
||||||
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') {
|
|
||||||
return (
|
return (
|
||||||
<DbcUpload
|
<a
|
||||||
onDbcLoaded={this.onDbcLoaded} />
|
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 (
|
return (
|
||||||
<div>
|
<GithubDbcList
|
||||||
<button className='button--inverted'
|
onDbcLoaded={this.onDbcLoaded}
|
||||||
onClick={ this.props.handleClose }>
|
repo={this.state.userOpenDbcRepo}
|
||||||
<span>Cancel</span>
|
openDbcClient={this.props.openDbcClient}
|
||||||
</button>
|
/>
|
||||||
<button className='button--primary'
|
|
||||||
onClick={ this.handleSave }>
|
|
||||||
<span>Load DBC</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} else if (tab === "Upload") {
|
||||||
|
return <DbcUpload onDbcLoaded={this.onDbcLoaded} />;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
renderActions() {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<div>
|
||||||
title='Load DBC File'
|
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||||
subtitle='Modify an existing DBC file with Cabana'
|
<span>Cancel</span>
|
||||||
handleClose={ this.props.handleClose }
|
</button>
|
||||||
navigation={ this.renderTabNavigation() }
|
<button className="button--primary" onClick={this.handleSave}>
|
||||||
actions={ this.renderActions() }>
|
<span>Load DBC</span>
|
||||||
{ this.renderTabContent() }
|
</button>
|
||||||
</Modal>
|
</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 React, { Component } from "react";
|
||||||
import { css, StyleSheet } from 'aphrodite/no-important';
|
import { css, StyleSheet } from "aphrodite/no-important";
|
||||||
|
|
||||||
const keyframes = {
|
const keyframes = {
|
||||||
'0%': {
|
"0%": {
|
||||||
transform: 'translateX(0)'
|
transform: "translateX(0)"
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
transform: 'translateX(-400px)'
|
transform: "translateX(-400px)"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const animationColor1 = 'RGBA(74, 242, 161, 1.00)';
|
const animationColor1 = "RGBA(74, 242, 161, 1.00)";
|
||||||
const animationColor2 = 'RGBA(140, 169, 197, 1.00)';
|
const animationColor2 = "RGBA(140, 169, 197, 1.00)";
|
||||||
|
|
||||||
const Styles = StyleSheet.create({
|
const Styles = StyleSheet.create({
|
||||||
loadingBar: {
|
loadingBar: {
|
||||||
display: 'block',
|
display: "block",
|
||||||
animationName: [keyframes],
|
animationName: [keyframes],
|
||||||
animationDuration: '2s',
|
animationDuration: "2s",
|
||||||
animationTimingFunction: 'linear',
|
animationTimingFunction: "linear",
|
||||||
animationIterationCount: 'infinite',
|
animationIterationCount: "infinite",
|
||||||
backgroundColor: animationColor1,
|
backgroundColor: animationColor1,
|
||||||
backgroundImage: `linear-gradient(to right,
|
backgroundImage: `linear-gradient(to right,
|
||||||
${animationColor2} 0,
|
${animationColor2} 0,
|
||||||
${animationColor2} 50%,
|
${animationColor2} 50%,
|
||||||
${animationColor1} 50%,
|
${animationColor1} 50%,
|
||||||
${animationColor1} 100%)`,
|
${animationColor1} 100%)`,
|
||||||
backgroundRepeat: 'repeat-x',
|
backgroundRepeat: "repeat-x",
|
||||||
backgroundSize: '25pc 25pc',
|
backgroundSize: "25pc 25pc",
|
||||||
width: '200%',
|
width: "200%",
|
||||||
position: 'fixed',
|
position: "fixed",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: 2
|
height: 2
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class LoadingBar extends Component {
|
export default class LoadingBar extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (<div className={css(Styles.loadingBar)}></div>)
|
return <div className={css(Styles.loadingBar)} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,113 +1,124 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export default class MessageBytes extends Component {
|
export default class MessageBytes extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
seekTime: PropTypes.number.isRequired,
|
seekTime: PropTypes.number.isRequired,
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
seekIndex: PropTypes.number,
|
seekIndex: PropTypes.number,
|
||||||
live: PropTypes.bool.isRequired,
|
live: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isVisible: true,
|
||||||
|
lastMessageIndex: 0,
|
||||||
|
lastSeekTime: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
||||||
super(props);
|
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
|
||||||
this.state = {
|
}
|
||||||
isVisible: true,
|
|
||||||
lastMessageIndex: 0,
|
|
||||||
lastSeekTime: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
|
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) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if(nextProps.live) {
|
this.updateCanvas(nextProps);
|
||||||
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);
|
findMostRecentMessage(seekTime) {
|
||||||
} else {
|
const { message } = this.props;
|
||||||
return nextProps.seekTime !== this.props.seekTime
|
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) {
|
if (!mostRecentMessageIndex) {
|
||||||
this.updateCanvas(nextProps);
|
// TODO this can be faster with binary search, not currently a bottleneck though.
|
||||||
|
|
||||||
|
mostRecentMessageIndex = message.entries.findIndex(
|
||||||
|
e => e.relTime >= seekTime
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
findMostRecentMessage(seekTime) {
|
if (mostRecentMessageIndex) {
|
||||||
const {message} = this.props;
|
this.setState({
|
||||||
const {lastMessageIndex, lastSeekTime} = this.state;
|
lastMessageIndex: mostRecentMessageIndex,
|
||||||
let mostRecentMessageIndex = null;
|
lastSeekTime: seekTime
|
||||||
if(seekTime >= lastSeekTime) {
|
});
|
||||||
for(let i = lastMessageIndex; i < message.entries.length; i++) {
|
return message.entries[mostRecentMessageIndex];
|
||||||
const msg = message.entries[i];
|
}
|
||||||
if(msg && msg.relTime >= seekTime) {
|
}
|
||||||
mostRecentMessageIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!mostRecentMessageIndex) {
|
updateCanvas(props) {
|
||||||
// TODO this can be faster with binary search, not currently a bottleneck though.
|
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) {
|
if (!mostRecentMsg) {
|
||||||
this.setState({lastMessageIndex: mostRecentMessageIndex, lastSeekTime: seekTime});
|
mostRecentMsg = message.entries[0];
|
||||||
return message.entries[mostRecentMessageIndex];
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCanvas(props) {
|
const ctx = this.canvas.getContext("2d");
|
||||||
const {message, live, seekTime} = props;
|
ctx.clearRect(0, 0, 180, 15);
|
||||||
if(!this.canvas || message.entries.length === 0) return;
|
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];
|
ctx.fillRect(i * 20, 0, 20, 15);
|
||||||
if(!live) {
|
|
||||||
mostRecentMsg = this.findMostRecentMessage(seekTime);
|
|
||||||
|
|
||||||
if(!mostRecentMsg) {
|
ctx.font = "12px Courier";
|
||||||
mostRecentMsg = message.entries[0];
|
ctx.fillStyle = "white";
|
||||||
}
|
ctx.fillText(hexData, i * 20 + 2, 12);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onVisibilityChange(isVisible) {
|
onVisibilityChange(isVisible) {
|
||||||
if(isVisible !== this.state.isVisible) {
|
if (isVisible !== this.state.isVisible) {
|
||||||
this.setState({isVisible});
|
this.setState({ isVisible });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onCanvasRefAvailable(ref) {
|
onCanvasRefAvailable(ref) {
|
||||||
if(!ref) return;
|
if (!ref) return;
|
||||||
|
|
||||||
this.canvas = ref;
|
this.canvas = ref;
|
||||||
this.canvas.width = 160 * window.devicePixelRatio;
|
this.canvas.width = 160 * window.devicePixelRatio;
|
||||||
this.canvas.height = 15 * window.devicePixelRatio;
|
this.canvas.height = 15 * window.devicePixelRatio;
|
||||||
const ctx = this.canvas.getContext('2d');
|
const ctx = this.canvas.getContext("2d");
|
||||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (<canvas ref={this.onCanvasRefAvailable}
|
return (
|
||||||
className='cabana-meta-messages-list-item-bytes-canvas'></canvas>);
|
<canvas
|
||||||
}
|
ref={this.onCanvasRefAvailable}
|
||||||
|
className="cabana-meta-messages-list-item-bytes-canvas"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
import { StyleSheet, css } from "aphrodite/no-important";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import GlobalStyles from '../styles/styles';
|
import GlobalStyles from "../styles/styles";
|
||||||
import Images from '../styles/images';
|
import Images from "../styles/images";
|
||||||
|
|
||||||
export default class Modal extends Component {
|
export default class Modal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -21,112 +21,119 @@ export default class Modal extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onKeyDown(e) {
|
_onKeyDown(e) {
|
||||||
if(e.keyCode === 27){ // escape
|
if (e.keyCode === 27) {
|
||||||
this.props.onCancel()
|
// escape
|
||||||
|
this.props.onCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
document.addEventListener('keydown', this._onKeyDown);
|
document.addEventListener("keydown", this._onKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this._onKeyDown);
|
document.removeEventListener("keydown", this._onKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectButton() {
|
selectButton() {
|
||||||
const {continueEnabled, continueText, onContinue} = this.props;
|
const { continueEnabled, continueText, onContinue } = this.props;
|
||||||
let style;
|
let style;
|
||||||
if(continueEnabled) {
|
if (continueEnabled) {
|
||||||
style = Styles.selectButtonEnabled;
|
style = Styles.selectButtonEnabled;
|
||||||
} else {
|
} else {
|
||||||
style = Styles.selectButtonDisabled;
|
style = Styles.selectButtonDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<div className={css(GlobalStyles.button, Styles.selectButton, style)}
|
return (
|
||||||
onClick={continueEnabled ? this.props.onContinue : () => {}}>
|
<div
|
||||||
<p>{continueText || 'Continue'}</p>
|
className={css(GlobalStyles.button, Styles.selectButton, style)}
|
||||||
</div>);
|
onClick={continueEnabled ? this.props.onContinue : () => {}}
|
||||||
|
>
|
||||||
|
<p>{continueText || "Continue"}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (<div className={css(Styles.root)}>
|
return (
|
||||||
<div className={css(Styles.bg)}></div>
|
<div className={css(Styles.root)}>
|
||||||
<div className={css(Styles.box)}>
|
<div className={css(Styles.bg)} />
|
||||||
<div className={css(Styles.header)}>
|
<div className={css(Styles.box)}>
|
||||||
<p className={css(Styles.title)}>{this.props.title}</p>
|
<div className={css(Styles.header)}>
|
||||||
<Images.clear styles={[Styles.closeButton]}
|
<p className={css(Styles.title)}>{this.props.title}</p>
|
||||||
onClick={this.props.onCancel} />
|
<Images.clear
|
||||||
</div>
|
styles={[Styles.closeButton]}
|
||||||
{this.props.children}
|
onClick={this.props.onCancel}
|
||||||
<div className={css(Styles.select)}>
|
/>
|
||||||
{this.selectButton()}
|
</div>
|
||||||
<div className={css(Styles.finishButton, Styles.cancelButton)}>
|
{this.props.children}
|
||||||
<p onClick={this.props.onCancel}>Cancel</p>
|
<div className={css(Styles.select)}>
|
||||||
</div>
|
{this.selectButton()}
|
||||||
</div>
|
<div className={css(Styles.finishButton, Styles.cancelButton)}>
|
||||||
</div>
|
<p onClick={this.props.onCancel}>Cancel</p>
|
||||||
</div>);
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const Styles = StyleSheet.create({
|
const Styles = StyleSheet.create({
|
||||||
bg: {
|
bg: {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 9,
|
zIndex: 9,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
backgroundColor: 'white',
|
backgroundColor: "white",
|
||||||
opacity: 0.75
|
opacity: 0.75
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
marginRight: 'auto'
|
marginRight: "auto"
|
||||||
},
|
|
||||||
closeButton: {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
closeButton: {},
|
||||||
box: {
|
box: {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
top: '50%',
|
top: "50%",
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: "translate(-50%, -50%)",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: 'white',
|
backgroundColor: "white",
|
||||||
borderRadius: '4px',
|
borderRadius: "4px",
|
||||||
border: '1px solid #000',
|
border: "1px solid #000",
|
||||||
boxShadow: '1px 1px 1px #000',
|
boxShadow: "1px 1px 1px #000",
|
||||||
padding: 20,
|
padding: 20,
|
||||||
minWidth: 480
|
minWidth: 480
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-end'
|
justifyContent: "flex-end"
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
paddingTop: 20,
|
paddingTop: 20,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
},
|
},
|
||||||
finishButton: {
|
finishButton: {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 80,
|
width: 80,
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
},
|
},
|
||||||
selectButton: {
|
selectButton: {
|
||||||
backgroundColor: 'rgb(77,144,254)',
|
backgroundColor: "rgb(77,144,254)",
|
||||||
color: 'white'
|
color: "white"
|
||||||
},
|
},
|
||||||
selectButtonDisabled: {
|
selectButtonDisabled: {
|
||||||
cursor: 'default',
|
cursor: "default",
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,295 +1,376 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import Moment from 'moment';
|
import Moment from "moment";
|
||||||
import _ from 'lodash';
|
import _ from "lodash";
|
||||||
import cx from 'classnames';
|
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 {
|
export default class OnboardingModal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
handlePandaConnect: PropTypes.func,
|
handlePandaConnect: PropTypes.func,
|
||||||
routes: PropTypes.array,
|
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 = {
|
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
|
||||||
step2: require("../../images/webusb-enable-experimental-features.png"),
|
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
|
||||||
step3: require("../../images/webusb-enable-webusb.png"),
|
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) {
|
toggleUsbInstructions() {
|
||||||
super(props);
|
this.setState({
|
||||||
|
viewingUsbInstructions: !this.state.viewingUsbInstructions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
navigateToAuth() {
|
||||||
webUsbEnabled: !!navigator.usb,
|
const authUrl = auth.authUrl();
|
||||||
viewingUsbInstructions: false,
|
window.location.href = authUrl;
|
||||||
pandaConnected: false,
|
}
|
||||||
chffrDrivesSearch: '',
|
|
||||||
chffrDrivesSortBy: 'start_time',
|
|
||||||
chffrDrivesOrderDesc: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
|
filterRoutesWithCan(drive) {
|
||||||
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
|
return drive.can === true;
|
||||||
this.handleSortDrives = this.handleSortDrives.bind(this);
|
}
|
||||||
this.handleSearchDrives = this.handleSearchDrives.bind(this);
|
|
||||||
this.navigateToAuth = this.navigateToAuth.bind(this);
|
handleSearchDrives(drive) {
|
||||||
this.openChffrDrive = this.openChffrDrive.bind(this);
|
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() {
|
openChffrDrive(route) {
|
||||||
if (!this.state.webUsbEnabled) { return };
|
window.location.search = `?route=${route.fullname}`;
|
||||||
this.props.handlePandaConnect();
|
}
|
||||||
|
|
||||||
|
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() {
|
renderChffrOption() {
|
||||||
this.setState({ viewingUsbInstructions: !this.state.viewingUsbInstructions });
|
const { routes } = this.props;
|
||||||
}
|
if (routes.length > 0) {
|
||||||
|
return (
|
||||||
navigateToAuth() {
|
<div className="cabana-onboarding-mode-chffr">
|
||||||
const authUrl = auth.authUrl();
|
<div className="cabana-onboarding-mode-chffr-search">
|
||||||
window.location.href = authUrl;
|
<div className="form-field--small">
|
||||||
}
|
<input
|
||||||
|
type="text"
|
||||||
filterRoutesWithCan(drive) {
|
id="chffr_drives_search"
|
||||||
return drive.can === true;
|
placeholder="Search chffr drives"
|
||||||
}
|
value={this.state.chffrDrivesSearch}
|
||||||
|
onChange={e =>
|
||||||
handleSearchDrives(drive) {
|
this.setState({ chffrDrivesSearch: e.target.value })
|
||||||
const { chffrDrivesSearch } = this.state;
|
}
|
||||||
const searchKeywords = chffrDrivesSearch.split(" ")
|
/>
|
||||||
.filter((s) => s.length > 0)
|
<div className="cabana-onboarding-mode-chffr-search-helper">
|
||||||
.map((s) => s.toLowerCase());
|
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
|
||||||
|
</div>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
<div
|
||||||
|
className={cx("cabana-onboarding-mode-chffr-header", {
|
||||||
renderUsbInstructions() {
|
"is-ordered-desc": this.state.chffrDrivesOrderDesc,
|
||||||
return (
|
"is-ordered-asc": !this.state.chffrDrivesOrderDesc
|
||||||
<div className='cabana-onboarding-instructions'>
|
})}
|
||||||
<button className='button--small button--inverted' onClick={ this.toggleUsbInstructions }>
|
>
|
||||||
<i className='fa fa-chevron-left'></i>
|
<div
|
||||||
<span> Go back</span>
|
className={cx("cabana-onboarding-mode-chffr-drive-date", {
|
||||||
</button>
|
"is-sorted": this.state.chffrDrivesSortBy === "start_time"
|
||||||
<h3>Follow these directions to enable WebUSB:</h3>
|
})}
|
||||||
<ol className='cabana-onboarding-instructions-list list--bubbled'>
|
onClick={() => this.handleSortDrives("start_time")}
|
||||||
<li>
|
>
|
||||||
<p><strong>Open your Chrome settings:</strong></p>
|
<span>Date</span>
|
||||||
<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>
|
||||||
)
|
<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() {
|
renderOnboardingOptions() {
|
||||||
if (this.state.viewingUsbInstructions) {
|
return (
|
||||||
return this.renderUsbInstructions();
|
<div className="cabana-onboarding-modes">
|
||||||
}
|
<div className="cabana-onboarding-mode">{this.renderChffrOption()}</div>
|
||||||
else {
|
<div className="cabana-onboarding-mode">
|
||||||
return this.renderOnboardingOptions();
|
<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() {
|
renderUsbInstructions() {
|
||||||
return (
|
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>
|
<p>
|
||||||
<span>Don't have a <a href='https://panda.comma.ai' target='_blank'>panda</a>? </span>
|
<strong>Open your Chrome settings:</strong>
|
||||||
<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>
|
</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() {
|
renderModalContent() {
|
||||||
return (
|
if (this.state.viewingUsbInstructions) {
|
||||||
<Modal
|
return this.renderUsbInstructions();
|
||||||
title='Welcome to cabana'
|
} else {
|
||||||
subtitle='Get started by viewing your chffr drives or enabling live mode'
|
return this.renderOnboardingOptions();
|
||||||
footer={ this.renderModalFooter() }
|
|
||||||
disableClose={ true }
|
|
||||||
variations={['wide', 'dark']}>
|
|
||||||
{ this.renderModalContent() }
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
import Measure from 'react-measure';
|
import Measure from "react-measure";
|
||||||
|
|
||||||
export default class Modal extends Component {
|
export default class Modal extends Component {
|
||||||
static PropTypes = {
|
static PropTypes = {
|
||||||
variations: PropTypes.array,
|
variations: PropTypes.array,
|
||||||
disableClose: PropTypes.bool,
|
disableClose: PropTypes.bool,
|
||||||
handleClose: PropTypes.func,
|
handleClose: PropTypes.func,
|
||||||
handleSave: PropTypes.func,
|
handleSave: PropTypes.func,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
navigation: PropTypes.node,
|
navigation: PropTypes.node,
|
||||||
actions: PropTypes.node,
|
actions: PropTypes.node,
|
||||||
footer: 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) {
|
checkClosability() {
|
||||||
super(props);
|
return this.props.disableClose || false;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
checkYScrollability() {
|
||||||
windowHeight: {},
|
return this.state.modalHeight > this.state.windowHeight;
|
||||||
modalHeight: {},
|
}
|
||||||
};
|
|
||||||
|
|
||||||
this.updateHeights = this.updateHeights.bind(this);
|
render() {
|
||||||
}
|
return (
|
||||||
|
<div
|
||||||
updateHeights(contentRect) {
|
className={cx("cabana-modal", this.readVariationClasses(), {
|
||||||
this.setState({ windowHeight: window.innerHeight });
|
"cabana-modal--not-closable": this.checkClosability(),
|
||||||
this.setState({ modalHeight: contentRect.bounds.height });
|
"cabana-modal--scrollable-y": this.checkYScrollability()
|
||||||
}
|
})}
|
||||||
|
>
|
||||||
readVariationClasses() {
|
<Measure
|
||||||
if (this.props.variations) {
|
bounds
|
||||||
const { variations } = this.props;
|
onResize={contentRect => {
|
||||||
const classes = variations.reduce((classes, variation) =>
|
this.updateHeights(contentRect);
|
||||||
classes + `cabana-modal--${variation} `,
|
}}
|
||||||
'');
|
>
|
||||||
return classes;
|
{({ measureRef }) => (
|
||||||
}
|
<div ref={measureRef} className="cabana-modal-container">
|
||||||
}
|
<div
|
||||||
|
className="cabana-modal-close-icon"
|
||||||
checkClosability() {
|
onClick={this.props.handleClose}
|
||||||
return (this.props.disableClose || false);
|
/>
|
||||||
}
|
<div className="cabana-modal-header">
|
||||||
|
<h1>{this.props.title}</h1>
|
||||||
checkYScrollability() {
|
<p>{this.props.subtitle}</p>
|
||||||
return (this.state.modalHeight > this.state.windowHeight);
|
</div>
|
||||||
}
|
<div className="cabana-modal-navigation">
|
||||||
|
{this.props.navigation}
|
||||||
render() {
|
</div>
|
||||||
return (
|
<div className="cabana-modal-body">
|
||||||
<div className={ cx(
|
<div className="cabana-modal-body-window">
|
||||||
'cabana-modal',
|
{this.props.children}
|
||||||
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 }>
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
</Measure>
|
||||||
|
<div
|
||||||
|
className="cabana-modal-backdrop"
|
||||||
|
onClick={this.props.handleClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +1,137 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import {PART_SEGMENT_LENGTH} from '../config';
|
import { PART_SEGMENT_LENGTH } from "../config";
|
||||||
|
|
||||||
export default class PartSelector extends Component {
|
export default class PartSelector extends Component {
|
||||||
static selectorWidth = 150;
|
static selectorWidth = 150;
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onPartChange: PropTypes.func.isRequired,
|
onPartChange: PropTypes.func.isRequired,
|
||||||
partsCount: PropTypes.number.isRequired,
|
partsCount: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
|
||||||
|
selectedPart: 0,
|
||||||
|
isDragging: false
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
this.selectNextPart = this.selectNextPart.bind(this);
|
||||||
super(props);
|
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 = {
|
makePartStyle(partsCount, selectedPart) {
|
||||||
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
|
return {
|
||||||
selectedPart: 0,
|
left: selectedPart / partsCount * PartSelector.selectorWidth,
|
||||||
isDragging: false,
|
width: PART_SEGMENT_LENGTH / partsCount * PartSelector.selectorWidth
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.selectNextPart = this.selectNextPart.bind(this);
|
componentWillReceiveProps(nextProps) {
|
||||||
this.selectPrevPart = this.selectPrevPart.bind(this);
|
if (nextProps.partsCount !== this.props.partsCount) {
|
||||||
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
|
const selectedPartStyle = this.makePartStyle(
|
||||||
this.onSelectedPartMouseMove = this.onSelectedPartMouseMove.bind(this);
|
nextProps.partsCount,
|
||||||
this.onSelectedPartDragEnd = this.onSelectedPartDragEnd.bind(this);
|
this.state.selectedPart
|
||||||
this.onClick = this.onClick.bind(this);
|
);
|
||||||
|
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) {
|
this.props.onPartChange(part);
|
||||||
return {
|
this.setState({
|
||||||
left: (selectedPart / partsCount) * PartSelector.selectorWidth,
|
selectedPart: part,
|
||||||
width: (PART_SEGMENT_LENGTH / partsCount) * PartSelector.selectorWidth
|
selectedPartStyle: this.makePartStyle(this.props.partsCount, part)
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNextPart() {
|
||||||
|
let { selectedPart } = this.state;
|
||||||
|
selectedPart++;
|
||||||
|
if (selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
this.selectPart(selectedPart);
|
||||||
if(nextProps.partsCount !== this.props.partsCount) {
|
}
|
||||||
const selectedPartStyle = this.makePartStyle(nextProps.partsCount, this.state.selectedPart);
|
|
||||||
this.setState({selectedPartStyle});
|
selectPrevPart() {
|
||||||
}
|
let { selectedPart } = this.state;
|
||||||
|
selectedPart--;
|
||||||
|
if (selectedPart < 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPart(part) {
|
this.selectPart(selectedPart);
|
||||||
part = Math.max(0, Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part));
|
}
|
||||||
if(part === this.state.selectedPart){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onPartChange(part);
|
partAtClientX(clientX) {
|
||||||
this.setState({selectedPart: part,
|
const rect = this.selectorRect.getBoundingClientRect();
|
||||||
selectedPartStyle: this.makePartStyle(this.props.partsCount,
|
const x = clientX - rect.left;
|
||||||
part)});
|
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNextPart() {
|
onSelectedPartDragStart(e) {
|
||||||
let {selectedPart} = this.state;
|
this.setState({ isDragging: true });
|
||||||
selectedPart++;
|
document.addEventListener("mouseup", this.onSelectedPartDragEnd);
|
||||||
if(selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
|
}
|
||||||
return;
|
|
||||||
}
|
onSelectedPartMouseMove(e) {
|
||||||
|
if (!this.state.isDragging) return;
|
||||||
this.selectPart(selectedPart);
|
|
||||||
}
|
const part = this.partAtClientX(e.clientX);
|
||||||
|
this.selectPart(part);
|
||||||
selectPrevPart() {
|
}
|
||||||
let {selectedPart} = this.state;
|
|
||||||
selectedPart--;
|
onSelectedPartDragEnd(e) {
|
||||||
if(selectedPart < 0) {
|
this.setState({ isDragging: false });
|
||||||
return;
|
document.removeEventListener("mouseup", this.onSelectedPartDragEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectPart(selectedPart);
|
onClick(e) {
|
||||||
}
|
const part = this.partAtClientX(e.clientX);
|
||||||
|
this.selectPart(part);
|
||||||
partAtClientX(clientX) {
|
}
|
||||||
const rect = this.selectorRect.getBoundingClientRect();
|
|
||||||
const x = clientX - rect.left;
|
render() {
|
||||||
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
|
const { selectedPartStyle } = this.state;
|
||||||
}
|
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
|
||||||
|
// all parts are available so no need to render the partselector
|
||||||
onSelectedPartDragStart(e) {
|
|
||||||
this.setState({isDragging: true});
|
return null;
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
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 React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export default class PlayButton extends Component {
|
export default class PlayButton extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -11,33 +11,41 @@ export default class PlayButton extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {'hover': false};
|
this.state = { hover: false };
|
||||||
|
|
||||||
this.onClick = this.onClick.bind(this);
|
this.onClick = this.onClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
imageSource() {
|
imageSource() {
|
||||||
const {hover} = this.state;
|
const { hover } = this.state;
|
||||||
const {isPlaying} = this.props;
|
const { isPlaying } = this.props;
|
||||||
if(isPlaying) {
|
if (isPlaying) {
|
||||||
if(hover) {
|
if (hover) {
|
||||||
return (process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg");
|
return (
|
||||||
|
process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg"
|
||||||
|
);
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
if(hover) {
|
if (hover) {
|
||||||
return (process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg");
|
return (
|
||||||
|
process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg"
|
||||||
|
);
|
||||||
} else {
|
} 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) {
|
onClick(e) {
|
||||||
let {isPlaying} = this.props;
|
let { isPlaying } = this.props;
|
||||||
|
|
||||||
if(!isPlaying) {
|
if (!isPlaying) {
|
||||||
this.props.onPlay();
|
this.props.onPlay();
|
||||||
} else {
|
} else {
|
||||||
this.props.onPause();
|
this.props.onPause();
|
||||||
|
@ -45,11 +53,15 @@ export default class PlayButton extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <img src={this.imageSource()}
|
return (
|
||||||
alt={this.props.isPlaying ? 'Pause' : 'Play'}
|
<img
|
||||||
className={this.props.className}
|
src={this.imageSource()}
|
||||||
onClick={this.onClick}
|
alt={this.props.isPlaying ? "Pause" : "Play"}
|
||||||
onMouseOver={() => this.setState({hover: true})}
|
className={this.props.className}
|
||||||
onMouseLeave={() => this.setState({hover: false})} />;
|
onClick={this.onClick}
|
||||||
|
onMouseOver={() => this.setState({ hover: true })}
|
||||||
|
onMouseLeave={() => this.setState({ hover: false })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,220 +1,237 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import PlayButton from '../PlayButton';
|
import PlayButton from "../PlayButton";
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from "../../utils/debounce";
|
||||||
|
|
||||||
export default class RouteSeeker extends Component {
|
export default class RouteSeeker extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
secondsLoaded: PropTypes.number.isRequired,
|
secondsLoaded: PropTypes.number.isRequired,
|
||||||
segmentIndices: PropTypes.arrayOf(PropTypes.number),
|
segmentIndices: PropTypes.arrayOf(PropTypes.number),
|
||||||
onUserSeek: PropTypes.func,
|
onUserSeek: PropTypes.func,
|
||||||
onPlaySeek: PropTypes.func,
|
onPlaySeek: PropTypes.func,
|
||||||
video: PropTypes.node,
|
video: PropTypes.node,
|
||||||
onPause: PropTypes.func,
|
onPause: PropTypes.func,
|
||||||
onPlay: PropTypes.func,
|
onPlay: PropTypes.func,
|
||||||
playing: PropTypes.bool,
|
playing: PropTypes.bool,
|
||||||
segmentProgress: PropTypes.func,
|
segmentProgress: PropTypes.func,
|
||||||
ratioTime: PropTypes.func,
|
ratioTime: PropTypes.func,
|
||||||
nearestFrameTime: PropTypes.number
|
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};
|
this.onMouseMove = this.onMouseMove.bind(this);
|
||||||
static zeroSeekedBarStyle = {width: 0};
|
this.onMouseLeave = this.onMouseLeave.bind(this);
|
||||||
static hiddenTooltipStyle = {display: 'none', left: 0};
|
this.onMouseDown = this.onMouseDown.bind(this);
|
||||||
static markerWidth = 20;
|
this.onMouseUp = this.onMouseUp.bind(this);
|
||||||
static tooltipWidth = 50;
|
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) {
|
componentWillReceiveProps(nextProps) {
|
||||||
super(props);
|
const { ratio } = this.state;
|
||||||
this.state = {
|
|
||||||
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
|
||||||
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
|
||||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
|
||||||
ratio: 0,
|
|
||||||
tooltipTime: '0:00',
|
|
||||||
isPlaying: false,
|
|
||||||
isDragging: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onMouseMove = this.onMouseMove.bind(this);
|
if (
|
||||||
this.onMouseLeave = this.onMouseLeave.bind(this);
|
JSON.stringify(this.props.segmentIndices) !==
|
||||||
this.onMouseDown = this.onMouseDown.bind(this);
|
JSON.stringify(nextProps.segmentIndices)
|
||||||
this.onMouseUp = this.onMouseUp.bind(this);
|
) {
|
||||||
this.onClick = this.onClick.bind(this);
|
this.setState({
|
||||||
this.onPlay = this.onPlay.bind(this);
|
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
||||||
this.onPause = this.onPause.bind(this);
|
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
||||||
this.executePlayTimer = this.executePlayTimer.bind(this);
|
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) {
|
if (this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
|
||||||
const {ratio} = this.state;
|
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
|
||||||
|
this.updateSeekedBar(newRatio);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
if (nextProps.playing && !this.state.isPlaying) {
|
||||||
window.cancelAnimationFrame(this.playTimer);
|
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) {
|
this.setState({
|
||||||
const rect = this.progressBar.getBoundingClientRect();
|
markerStyle,
|
||||||
const x = e.clientX - rect.left;
|
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) {
|
if (newRatio === this.state.ratio) {
|
||||||
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||||
if(markerOffsetPct < 0) {
|
return;
|
||||||
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)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseLeave(e) {
|
if (newRatio >= 1) {
|
||||||
this.setState({markerStyle: RouteSeeker.hiddenMarkerStyle,
|
newRatio = 0;
|
||||||
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
this.props.onUserSeek(newRatio);
|
||||||
isDragging: false});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSeekedBar(ratio) {
|
if (newRatio >= 0) {
|
||||||
const seekedBarStyle = { width: (100 * ratio) + '%' };
|
this.updateSeekedBar(newRatio);
|
||||||
this.setState({seekedBarStyle, ratio})
|
this.props.onPlaySeek(currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(e) {
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
||||||
let ratio = this.mouseEventXOffsetPercent(e) / 100;
|
}
|
||||||
ratio = Math.min(1, Math.max(0, ratio));
|
|
||||||
this.updateSeekedBar(ratio);
|
onPause() {
|
||||||
this.props.onUserSeek(ratio);
|
window.cancelAnimationFrame(this.playTimer);
|
||||||
|
this.setState({ isPlaying: false });
|
||||||
|
this.props.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown() {
|
||||||
|
if (!this.state.isDragging) {
|
||||||
|
this.setState({ isDragging: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp() {
|
||||||
onPlay() {
|
if (this.state.isDragging) {
|
||||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
this.setState({ isDragging: false });
|
||||||
let {ratio} = this.state;
|
|
||||||
if(ratio >= 1) {
|
|
||||||
ratio = 0;
|
|
||||||
}
|
|
||||||
this.setState({isPlaying: true, ratio});
|
|
||||||
this.props.onPlay();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
executePlayTimer() {
|
render() {
|
||||||
const {videoElement} = this.props;
|
const { seekedBarStyle, markerStyle, tooltipStyle } = this.state;
|
||||||
if(videoElement === null) {
|
return (
|
||||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
<div className="cabana-explorer-visuals-camera-seeker">
|
||||||
return;
|
<PlayButton
|
||||||
}
|
className={"cabana-explorer-visuals-camera-seeker-playbutton"}
|
||||||
|
onPlay={this.onPlay}
|
||||||
const {currentTime} = videoElement;
|
onPause={this.onPause}
|
||||||
let newRatio = this.props.segmentProgress(currentTime);
|
isPlaying={this.state.isPlaying}
|
||||||
|
/>
|
||||||
if(newRatio === this.state.ratio) {
|
<div
|
||||||
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
className={"cabana-explorer-visuals-camera-seeker-progress"}
|
||||||
return;
|
onMouseMove={this.onMouseMove}
|
||||||
}
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
onMouseDown={this.onMouseDown}
|
||||||
if(newRatio >= 1) {
|
onMouseUp={this.onMouseUp}
|
||||||
newRatio = 0;
|
onClick={this.onClick}
|
||||||
this.props.onUserSeek(newRatio);
|
ref={ref => (this.progressBar = ref)}
|
||||||
}
|
>
|
||||||
|
<div
|
||||||
if(newRatio >= 0) {
|
className={"cabana-explorer-visuals-camera-seeker-progress-tooltip"}
|
||||||
this.updateSeekedBar(newRatio);
|
style={tooltipStyle}
|
||||||
this.props.onPlaySeek(currentTime);
|
>
|
||||||
}
|
{this.state.tooltipTime}
|
||||||
|
|
||||||
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>
|
|
||||||
</div>
|
</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';
|
import RouteSeeker from "./RouteSeeker";
|
||||||
export default RouteSeeker;
|
export default RouteSeeker;
|
||||||
|
|
|
@ -1,194 +1,205 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
import { StyleSheet, css } from "aphrodite/no-important";
|
||||||
|
|
||||||
import HLS from './HLS';
|
import HLS from "./HLS";
|
||||||
import {cameraPath} from '../api/routes';
|
import { cameraPath } from "../api/routes";
|
||||||
import Video from '../api/video';
|
import Video from "../api/video";
|
||||||
import RouteSeeker from './RouteSeeker/RouteSeeker';
|
import RouteSeeker from "./RouteSeeker/RouteSeeker";
|
||||||
|
|
||||||
const Styles = StyleSheet.create({
|
const Styles = StyleSheet.create({
|
||||||
loadingOverlay: {
|
loadingOverlay: {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
zIndex: 3
|
zIndex: 3
|
||||||
},
|
},
|
||||||
loadingSpinner: {
|
loadingSpinner: {
|
||||||
width: '25%',
|
width: "25%",
|
||||||
height: '25%',
|
height: "25%",
|
||||||
display: 'block'
|
display: "block"
|
||||||
},
|
},
|
||||||
img: {
|
img: {
|
||||||
height: 480,
|
height: 480,
|
||||||
display: 'block',
|
display: "block",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
zIndex: 2
|
zIndex: 2
|
||||||
},
|
},
|
||||||
hls: {
|
hls: {
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
height: 480,
|
height: 480,
|
||||||
backgroundColor: 'rgba(0,0,0,0.9)'
|
backgroundColor: "rgba(0,0,0,0.9)"
|
||||||
},
|
},
|
||||||
seekBar: {
|
seekBar: {
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
zIndex: 4
|
zIndex: 4
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default class RouteVideoSync extends Component {
|
export default class RouteVideoSync extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
userSeekIndex: PropTypes.number.isRequired,
|
userSeekIndex: PropTypes.number.isRequired,
|
||||||
secondsLoaded: PropTypes.number.isRequired,
|
secondsLoaded: PropTypes.number.isRequired,
|
||||||
startOffset: PropTypes.number.isRequired,
|
startOffset: PropTypes.number.isRequired,
|
||||||
message: PropTypes.object,
|
message: PropTypes.object,
|
||||||
firstCanTime: PropTypes.number.isRequired,
|
firstCanTime: PropTypes.number.isRequired,
|
||||||
canFrameOffset: PropTypes.number.isRequired,
|
canFrameOffset: PropTypes.number.isRequired,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
playing: PropTypes.bool.isRequired,
|
playing: PropTypes.bool.isRequired,
|
||||||
onPlaySeek: PropTypes.func.isRequired,
|
onPlaySeek: PropTypes.func.isRequired,
|
||||||
onUserSeek: PropTypes.func.isRequired,
|
onUserSeek: PropTypes.func.isRequired,
|
||||||
onPlay: PropTypes.func.isRequired,
|
onPlay: PropTypes.func.isRequired,
|
||||||
onPause: PropTypes.func.isRequired,
|
onPause: PropTypes.func.isRequired,
|
||||||
userSeekTime: PropTypes.number.isRequired
|
userSeekTime: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
shouldShowJpeg: true,
|
||||||
|
isLoading: true,
|
||||||
|
videoElement: null,
|
||||||
|
shouldRestartHls: false
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
this.onLoadStart = this.onLoadStart.bind(this);
|
||||||
super(props);
|
this.onLoadEnd = this.onLoadEnd.bind(this);
|
||||||
this.state = {
|
this.segmentProgress = this.segmentProgress.bind(this);
|
||||||
shouldShowJpeg: true,
|
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
|
||||||
isLoading: true,
|
this.onUserSeek = this.onUserSeek.bind(this);
|
||||||
videoElement: null,
|
this.onHlsRestart = this.onHlsRestart.bind(this);
|
||||||
shouldRestartHls: false,
|
this.ratioTime = this.ratioTime.bind(this);
|
||||||
};
|
}
|
||||||
|
|
||||||
this.onLoadStart = this.onLoadStart.bind(this);
|
componentWillReceiveProps(nextProps) {
|
||||||
this.onLoadEnd = this.onLoadEnd.bind(this);
|
if (
|
||||||
this.segmentProgress = this.segmentProgress.bind(this);
|
this.props.userSeekIndex !== nextProps.userSeekIndex ||
|
||||||
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
|
this.props.canFrameOffset !== nextProps.canFrameOffset ||
|
||||||
this.onUserSeek = this.onUserSeek.bind(this);
|
(this.props.message &&
|
||||||
this.onHlsRestart = this.onHlsRestart.bind(this);
|
nextProps.message &&
|
||||||
this.ratioTime = this.ratioTime.bind(this);
|
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) {
|
const ratio =
|
||||||
if(this.props.userSeekIndex !== nextProps.userSeekIndex
|
(currentTime - this.props.startOffset) / this.props.secondsLoaded;
|
||||||
|| this.props.canFrameOffset !== nextProps.canFrameOffset
|
return Math.max(0, Math.min(1, ratio));
|
||||||
|| (this.props.message
|
}
|
||||||
&& nextProps.message
|
|
||||||
&& this.props.message.entries.length !== nextProps.message.entries.length)) {
|
ratioTime(ratio) {
|
||||||
this.setState({shouldRestartHls: true});
|
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() {
|
onHlsRestart() {
|
||||||
const {url} = this.props;
|
this.setState({ shouldRestartHls: false });
|
||||||
const sec = Math.round(this.props.userSeekTime);
|
}
|
||||||
return cameraPath(url, sec);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingOverlay() {
|
render() {
|
||||||
return (<div className={css(Styles.loadingOverlay)}>
|
return (
|
||||||
<img className={css(Styles.loadingSpinner)}
|
<div className="cabana-explorer-visuals-camera">
|
||||||
src={process.env.PUBLIC_URL + "/img/loading.svg"}
|
{this.state.isLoading ? this.loadingOverlay() : null}
|
||||||
alt={"Loading video"}
|
{this.state.shouldShowJpeg ? (
|
||||||
/>
|
<img
|
||||||
</div>);
|
src={this.nearestFrameUrl()}
|
||||||
}
|
className={css(Styles.img)}
|
||||||
|
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)}
|
||||||
onLoadStart() {
|
/>
|
||||||
this.setState({shouldShowJpeg: true,
|
) : null}
|
||||||
isLoading: true});
|
<HLS
|
||||||
}
|
className={css(Styles.hls)}
|
||||||
|
source={Video.videoUrlForRouteUrl(this.props.url)}
|
||||||
onLoadEnd() {
|
startTime={this.props.userSeekTime}
|
||||||
this.setState({shouldShowJpeg: false,
|
playbackSpeed={1}
|
||||||
isLoading: false});
|
onVideoElementAvailable={this.onVideoElementAvailable}
|
||||||
}
|
playing={this.props.playing}
|
||||||
|
onClick={this.props.onVideoClick}
|
||||||
segmentProgress(currentTime) {
|
onLoadStart={this.onLoadStart}
|
||||||
// returns progress as number in [0,1]
|
onLoadEnd={this.onLoadEnd}
|
||||||
|
onUserSeek={this.onUserSeek}
|
||||||
if(currentTime < this.props.startOffset) {
|
onPlaySeek={this.props.onPlaySeek}
|
||||||
currentTime = this.props.startOffset;
|
segmentProgress={this.segmentProgress}
|
||||||
}
|
shouldRestart={this.state.shouldRestartHls}
|
||||||
|
onRestart={this.onHlsRestart}
|
||||||
const ratio = (currentTime - this.props.startOffset) / this.props.secondsLoaded;
|
/>
|
||||||
return Math.max(0, Math.min(1, ratio));
|
<RouteSeeker
|
||||||
}
|
className={css(Styles.seekBar)}
|
||||||
|
nearestFrameTime={this.props.userSeekTime}
|
||||||
ratioTime(ratio) {
|
segmentProgress={this.segmentProgress}
|
||||||
return ratio * this.props.secondsLoaded + this.props.startOffset;
|
secondsLoaded={this.props.secondsLoaded}
|
||||||
}
|
segmentIndices={this.props.segmentIndices}
|
||||||
|
onUserSeek={this.onUserSeek}
|
||||||
onVideoElementAvailable(videoElement) {
|
onPlaySeek={this.props.onPlaySeek}
|
||||||
this.setState({videoElement});
|
videoElement={this.state.videoElement}
|
||||||
}
|
onPlay={this.props.onPlay}
|
||||||
|
onPause={this.props.onPause}
|
||||||
onUserSeek(ratio) {
|
playing={this.props.playing}
|
||||||
/* ratio in [0,1] */
|
ratioTime={this.ratioTime}
|
||||||
|
/>
|
||||||
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
|
</div>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,227 +1,227 @@
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
import OpenDbc from '../api/OpenDbc';
|
import OpenDbc from "../api/OpenDbc";
|
||||||
import DBC from '../models/can/dbc';
|
import DBC from "../models/can/dbc";
|
||||||
import Modal from './Modals/baseModal';
|
import Modal from "./Modals/baseModal";
|
||||||
// import TabStyles from '../styles/modal-tabs';
|
// import TabStyles from '../styles/modal-tabs';
|
||||||
|
|
||||||
export default class SaveDbcModal extends Component {
|
export default class SaveDbcModal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dbc: PropTypes.instanceOf(DBC).isRequired,
|
dbc: PropTypes.instanceOf(DBC).isRequired,
|
||||||
sourceDbcFilename: PropTypes.string.isRequired,
|
sourceDbcFilename: PropTypes.string.isRequired,
|
||||||
handleClose: PropTypes.func.isRequired,
|
handleClose: PropTypes.func.isRequired,
|
||||||
onDbcSaved: PropTypes.func.isRequired,
|
onDbcSaved: PropTypes.func.isRequired,
|
||||||
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
|
||||||
hasGithubAuth: PropTypes.bool.isRequired,
|
hasGithubAuth: PropTypes.bool.isRequired,
|
||||||
loginWithGithub: PropTypes.element.isRequired
|
loginWithGithub: PropTypes.element.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
tab: "GitHub",
|
||||||
|
openDbcFork: null,
|
||||||
|
dbcFilename: this.props.sourceDbcFilename,
|
||||||
|
tabs: ["GitHub", "Download"]
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
this.commitToGitHub = this.commitToGitHub.bind(this);
|
||||||
super(props);
|
this.downloadDbcFile = this.downloadDbcFile.bind(this);
|
||||||
this.state = {
|
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
|
||||||
tab: 'GitHub',
|
this.renderForkButton = this.renderForkButton.bind(this);
|
||||||
openDbcFork: null,
|
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
||||||
dbcFilename: this.props.sourceDbcFilename,
|
this.renderActions = this.renderActions.bind(this);
|
||||||
tabs: ['GitHub', 'Download'],
|
}
|
||||||
};
|
|
||||||
|
|
||||||
this.commitToGitHub = this.commitToGitHub.bind(this);
|
async componentWillMount() {
|
||||||
this.downloadDbcFile = this.downloadDbcFile.bind(this);
|
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
|
||||||
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
|
this.setState({ openDbcFork });
|
||||||
this.renderForkButton = this.renderForkButton.bind(this);
|
}
|
||||||
this.renderTabNavigation = this.renderTabNavigation.bind(this);
|
|
||||||
this.renderActions = this.renderActions.bind(this);
|
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() {
|
async downloadDbcFile() {
|
||||||
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
|
const blob = new Blob([this.props.dbc.text()], {
|
||||||
this.setState({openDbcFork})
|
type: "text/plain;charset=utf-8"
|
||||||
}
|
});
|
||||||
|
const filename = this.state.dbcFilename.replace(/\.dbc/g, "") + ".dbc";
|
||||||
|
FileSaver.saveAs(blob, filename, true);
|
||||||
|
}
|
||||||
|
|
||||||
async commitToGitHub() {
|
async forkOpenDbcAndWait() {
|
||||||
const { openDbcFork, dbcFilename } = this.state;
|
const forkResponseSuccess = await this.props.openDbcClient.fork();
|
||||||
const filename = dbcFilename.replace(/\.dbc/g, '') + '.dbc';
|
if (forkResponseSuccess) {
|
||||||
const success = await this.props.openDbcClient.commitFile(openDbcFork,
|
let isTimedOut = false;
|
||||||
filename,
|
const timeout = window.setTimeout(() => {
|
||||||
this.props.dbc.text());
|
isTimedOut = true;
|
||||||
if (success) {
|
}, 30000);
|
||||||
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);
|
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
// fork failed
|
window.clearInterval(interval);
|
||||||
}
|
}
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
// fork failed
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
primaryActionDisabled() {
|
primaryActionDisabled() {
|
||||||
const { tab } = this.state;
|
const { tab } = this.state;
|
||||||
if (tab === 'GitHub') {
|
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() {
|
|
||||||
return (
|
return (
|
||||||
<div className='cabana-tabs-navigation'>
|
this.state.openDbcFork != null && this.state.dbcFilename.length > 0
|
||||||
{ this.state.tabs.map((tab) => {
|
);
|
||||||
return (
|
} else if (tab === "Download") {
|
||||||
<a className={ cx({'is-active': this.state.tab === tab})}
|
return true;
|
||||||
onClick={ () => { this.setState({ tab }) }}
|
}
|
||||||
key={tab}>
|
}
|
||||||
<span>{ tab }</span>
|
|
||||||
</a>
|
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>
|
</div>
|
||||||
)
|
);
|
||||||
|
} else if (tab === "Download") {
|
||||||
|
return <div>{this.renderFilenameField()}</div>;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderTabContent() {
|
renderActions() {
|
||||||
const { tab } = this.state;
|
const { tab } = this.state;
|
||||||
if (tab === 'GitHub') {
|
if (tab === "GitHub") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ this.renderForkStep() }
|
<button className="button--inverted" onClick={this.props.handleClose}>
|
||||||
{ this.renderFilenameField() }
|
<span>Cancel</span>
|
||||||
</div>
|
</button>
|
||||||
);
|
<button className="button--primary" onClick={this.commitToGitHub}>
|
||||||
}
|
<span>Commit to GitHub</span>
|
||||||
else if (tab === 'Download') {
|
</button>
|
||||||
return (
|
</div>
|
||||||
<div>
|
);
|
||||||
{ this.renderFilenameField() }
|
} else if (tab === "Download") {
|
||||||
</div>
|
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() {
|
render() {
|
||||||
const { tab } = this.state;
|
return (
|
||||||
if (tab === 'GitHub') {
|
<Modal
|
||||||
return (
|
title="Save DBC File"
|
||||||
<div>
|
subtitle="Save your progress and output to a DBC file"
|
||||||
<button className='button--inverted'
|
handleClose={this.props.handleClose}
|
||||||
onClick={ this.props.handleClose }>
|
navigation={this.renderTabNavigation()}
|
||||||
<span>Cancel</span>
|
actions={this.renderActions()}
|
||||||
</button>
|
>
|
||||||
<button className='button--primary'
|
{this.renderTabContent()}
|
||||||
onClick={ this.commitToGitHub }>
|
</Modal>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,365 +1,398 @@
|
||||||
// SignalLegendEntry.js
|
// SignalLegendEntry.js
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import cx from 'classnames';
|
import cx from "classnames";
|
||||||
|
|
||||||
import Signal from '../models/can/signal';
|
import Signal from "../models/can/signal";
|
||||||
import DbcUtils from '../utils/dbc';
|
import DbcUtils from "../utils/dbc";
|
||||||
import {swapKeysAndValues} from '../utils/object';
|
import { swapKeysAndValues } from "../utils/object";
|
||||||
|
|
||||||
export default class SignalLegendEntry extends Component {
|
export default class SignalLegendEntry extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
signal: PropTypes.instanceOf(Signal).isRequired,
|
signal: PropTypes.instanceOf(Signal).isRequired,
|
||||||
isHighlighted: PropTypes.bool,
|
isHighlighted: PropTypes.bool,
|
||||||
onSignalHover: PropTypes.func,
|
onSignalHover: PropTypes.func,
|
||||||
onSignalHoverEnd: PropTypes.func,
|
onSignalHoverEnd: PropTypes.func,
|
||||||
onTentativeSignalChange: PropTypes.func,
|
onTentativeSignalChange: PropTypes.func,
|
||||||
onSignalChange: PropTypes.func,
|
onSignalChange: PropTypes.func,
|
||||||
onSignalRemove: PropTypes.func,
|
onSignalRemove: PropTypes.func,
|
||||||
onSignalPlotChange: PropTypes.func,
|
onSignalPlotChange: PropTypes.func,
|
||||||
toggleExpandSignal: PropTypes.func,
|
toggleExpandSignal: PropTypes.func,
|
||||||
isPlotted: PropTypes.bool,
|
isPlotted: PropTypes.bool,
|
||||||
isExpanded: 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) => {
|
static fields = [
|
||||||
return (value, signal) => {
|
{
|
||||||
if(value !== '') {
|
field: "name",
|
||||||
value = Number(value) || 0;
|
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) {
|
if (isLittleEndian) {
|
||||||
value = 0;
|
// 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;
|
} else {
|
||||||
return signal;
|
// little endian -> big endian
|
||||||
};
|
const startByte = Math.floor(signal.startBit / 8),
|
||||||
};
|
endByte = Math.floor((signal.startBit + signal.size - 1) / 8);
|
||||||
|
|
||||||
static fields = [
|
if (startByte === endByte) {
|
||||||
{
|
signal.startBit = signal.startBit + signal.size - 1;
|
||||||
field: 'name',
|
} else {
|
||||||
title: 'Name',
|
signal.startBit = DbcUtils.bigEndianBitIndex(startBit);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
signal.isLittleEndian = isLittleEndian;
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
];
|
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) => {
|
static fieldSpecForName = name => {
|
||||||
return SignalLegendEntry.fields.find((field) =>
|
return SignalLegendEntry.fields.find(field => field.field === name);
|
||||||
field.field === name)
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isExpanded: false,
|
||||||
|
signalEdited: Object.assign(Object.create(props.signal), props.signal)
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
this.toggleEditing = this.toggleEditing.bind(this);
|
||||||
super(props);
|
this.updateField = this.updateField.bind(this);
|
||||||
this.state = {
|
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||||
isExpanded: false,
|
}
|
||||||
signalEdited: Object.assign(Object.create(props.signal), props.signal),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.toggleEditing = this.toggleEditing.bind(this);
|
componentWillReceiveProps(nextProps) {
|
||||||
this.updateField = this.updateField.bind(this);
|
if (!nextProps.signal.equals(this.props.signal)) {
|
||||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
this.setState({
|
||||||
}
|
signalEdited: Object.assign(
|
||||||
|
Object.create(nextProps.signal),
|
||||||
componentWillReceiveProps(nextProps) {
|
nextProps.signal
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderField(field, title, valueCol, signal) {
|
||||||
|
let titleStr;
|
||||||
|
if (typeof title === "function") {
|
||||||
|
titleStr = title(signal);
|
||||||
|
} else {
|
||||||
|
titleStr = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateField(fieldSpec, value) {
|
return (
|
||||||
let {signalEdited} = this.state;
|
<div key={field} className="form-field form-field--small">
|
||||||
const {signal} = this.props;
|
<label htmlFor={`${signal.name}_${field}`}>{titleStr}</label>
|
||||||
|
{valueCol}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(fieldSpec.transform) {
|
updateField(fieldSpec, value) {
|
||||||
signalEdited = fieldSpec.transform(value, signalEdited);
|
let { signalEdited } = this.state;
|
||||||
} else {
|
const { signal } = this.props;
|
||||||
signalEdited[fieldSpec.field] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (fieldSpec.transform) {
|
||||||
// Save entire signal while editing
|
signalEdited = fieldSpec.transform(value, signalEdited);
|
||||||
this.setState({signalEdited});
|
} else {
|
||||||
const signalCopy = Object.assign(Object.create(signal), signal);
|
signalEdited[fieldSpec.field] = value;
|
||||||
Object.entries(signalEdited).forEach(([field, value]) => {
|
|
||||||
signalCopy[field] = value;
|
|
||||||
});
|
|
||||||
this.props.onSignalChange(signalCopy, signal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNumberField(fieldSpec, signal) {
|
// Save entire signal while editing
|
||||||
const {field, title} = fieldSpec;
|
this.setState({ signalEdited });
|
||||||
let valueCol;
|
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) {
|
renderNumberField(fieldSpec, signal) {
|
||||||
let value = this.state.signalEdited[field];
|
const { field, title } = fieldSpec;
|
||||||
if(value !== '') {
|
let valueCol;
|
||||||
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) {
|
if (this.props.isExpanded) {
|
||||||
const {field, title} = fieldSpec;
|
let value = this.state.signalEdited[field];
|
||||||
let valueCol;
|
if (value !== "") {
|
||||||
if(this.props.isExpanded) {
|
let num = Number(value);
|
||||||
valueCol = (
|
value = isNaN(num) ? "" : num;
|
||||||
<input id={`${signal.name}_${field}`}
|
}
|
||||||
type="text"
|
valueCol = (
|
||||||
value={this.state.signalEdited[field] || ''}
|
<input
|
||||||
onChange={(e) => {
|
id={`${signal.name}_${field}`}
|
||||||
this.updateField(fieldSpec, e.target.value)
|
type="number"
|
||||||
}}
|
value={value}
|
||||||
/>);
|
onChange={e => {
|
||||||
} else {
|
this.updateField(fieldSpec, e.target.value);
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
|
} 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) {
|
return this.renderField(field, title, valueCol, signal);
|
||||||
const {signal, isPlotted} = this.props;
|
}
|
||||||
e.preventDefault();
|
|
||||||
this.props.onSignalPlotChange(!isPlotted, signal.uid);
|
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() {
|
return this.renderField(field, title, valueCol, signal);
|
||||||
const {signal} = this.props;
|
}
|
||||||
const expandedEntryClass = this.props.isExpanded ? 'is-expanded' : null;
|
|
||||||
const highlightedEntryClass = this.props.isHighlighted ? 'is-highlighted' : null;
|
renderFieldNode(field, signal) {
|
||||||
const plottedButtonClass = this.props.isPlotted ? 'button' : 'button--alpha';
|
if (field.type === "number") {
|
||||||
const plottedButtonText = this.props.isPlotted ? 'Hide Plot' : 'Show Plot';
|
return this.renderNumberField(field, signal);
|
||||||
return (
|
} 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
|
<div
|
||||||
className={cx('signals-legend-entry', expandedEntryClass, highlightedEntryClass)}
|
className="signals-legend-entry-header-name"
|
||||||
onMouseEnter={() => this.props.onSignalHover(signal)}
|
onClick={this.toggleEditing}
|
||||||
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}>
|
>
|
||||||
<div className='signals-legend-entry-color'
|
<strong>{signal.name}</strong>
|
||||||
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>
|
|
||||||
</div>
|
</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",
|
||||||
const ENV_GITHUB_CLIENT_ID = {debug: 'f1e42d14f45491f9ca34',
|
prod: "4b43250e7499a97d62a5"
|
||||||
prod: '4b43250e7499a97d62a5'}
|
};
|
||||||
export const GITHUB_CLIENT_ID = ENV_GITHUB_CLIENT_ID[ENV];
|
export const GITHUB_CLIENT_ID = ENV_GITHUB_CLIENT_ID[ENV];
|
||||||
|
|
||||||
const ENV_GITHUB_REDIRECT_URL = {debug: 'http://127.0.0.1:1235/callback',
|
const ENV_GITHUB_REDIRECT_URL = {
|
||||||
prod: 'https://api.comma.ai/cabana/ghcallback'}
|
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_REDIRECT_URL = ENV_GITHUB_REDIRECT_URL[ENV];
|
||||||
export const GITHUB_AUTH_TOKEN_KEY = 'gh_access_token';
|
export const GITHUB_AUTH_TOKEN_KEY = "gh_access_token";
|
||||||
export const OPENDBC_SOURCE_REPO = 'commaai/opendbc';
|
export const OPENDBC_SOURCE_REPO = "commaai/opendbc";
|
||||||
|
|
||||||
export const USE_UNLOGGER = (typeof window !== 'undefined' && getUrlParameter('unlogger') !== null);
|
export const USE_UNLOGGER =
|
||||||
export const UNLOGGER_HOST = 'http://localhost:8080/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;
|
export const PART_SEGMENT_LENGTH = 3;
|
||||||
|
|
||||||
|
@ -24,5 +28,5 @@ export const CAN_GRAPH_MAX_POINTS = 10000;
|
||||||
|
|
||||||
export const STREAMING_WINDOW = 60;
|
export const STREAMING_WINDOW = 60;
|
||||||
|
|
||||||
export const COMMA_ACCESS_TOKEN_COOKIE = 'comma_access_token';
|
export const COMMA_ACCESS_TOKEN_COOKIE = "comma_access_token";
|
||||||
export const COMMA_OAUTH_REDIRECT_COOKIE = 'wiki_login_redirect';
|
export const COMMA_OAUTH_REDIRECT_COOKIE = "wiki_login_redirect";
|
||||||
|
|
|
@ -1,49 +1,53 @@
|
||||||
import LogEntries from './LogEntries';
|
import LogEntries from "./LogEntries";
|
||||||
import {LOGENTRIES_TOKEN} from '../config';
|
import { LOGENTRIES_TOKEN } from "../config";
|
||||||
|
|
||||||
class CloudLog {
|
class CloudLog {
|
||||||
constructor() {
|
constructor() {
|
||||||
LogEntries.init({token: LOGENTRIES_TOKEN,
|
LogEntries.init({
|
||||||
no_format: true,
|
token: LOGENTRIES_TOKEN,
|
||||||
catchall: false});
|
no_format: true,
|
||||||
this.context = {};
|
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) {
|
const entry = {
|
||||||
this.context.update(obj);
|
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') {
|
log(message) {
|
||||||
if(typeof global.__JEST__ !== 'undefined') {
|
this.emit(message);
|
||||||
// Don't log in testing environment
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = {ctx: this.context,
|
warn(message) {
|
||||||
created: new Date().getTime() / 1000,
|
this.emit(message, "warn");
|
||||||
msg: message,
|
}
|
||||||
src: 'JSCloudLog'};
|
|
||||||
|
|
||||||
if(level === 'log') {
|
error(message) {
|
||||||
LogEntries.log(entry);
|
this.emit(message, "error");
|
||||||
} 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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Vendored from https://github.com/rapid7/le_js, which is broken with webpack.
|
||||||
/* eslint-disable */
|
/* 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 window = self;
|
||||||
}
|
}
|
||||||
var _indexOf = function (array, obj) {
|
var _indexOf = function(array, obj) {
|
||||||
for (var i = 0; i < array.length; i++) {
|
for (var i = 0; i < array.length; i++) {
|
||||||
if (obj === array[i]) {
|
if (obj === array[i]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Obtain a browser-specific XHR object
|
// Obtain a browser-specific XHR object
|
||||||
var _getAjaxObject = function () {
|
var _getAjaxObject = function() {
|
||||||
if (typeof XDomainRequest !== "undefined") {
|
if (typeof XDomainRequest !== "undefined") {
|
||||||
// We're using IE8/9
|
// We're using IE8/9
|
||||||
return new XDomainRequest();
|
return new XDomainRequest();
|
||||||
}
|
}
|
||||||
return new XMLHttpRequest();
|
return new XMLHttpRequest();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single log event stream.
|
* A single log event stream.
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
function LogStream(options) {
|
function LogStream(options) {
|
||||||
/**
|
/**
|
||||||
* @const
|
* @const
|
||||||
* @type {string} */
|
* @type {string} */
|
||||||
var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null;
|
var _traceCode = options.trace
|
||||||
/** @type {string} */
|
? (Math.random() + Math.PI).toString(36).substring(2, 10)
|
||||||
var _pageInfo = options.page_info;
|
: null;
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var _token = options.token;
|
var _pageInfo = options.page_info;
|
||||||
/** @type {boolean} */
|
/** @type {string} */
|
||||||
var _print = options.print;
|
var _token = options.token;
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
var _noFormat = options.no_format;
|
var _print = options.print;
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
var _SSL = function() {
|
var _noFormat = options.no_format;
|
||||||
if (typeof XDomainRequest === "undefined") {
|
/** @type {boolean} */
|
||||||
return options.ssl;
|
var _SSL = (function() {
|
||||||
}
|
if (typeof XDomainRequest === "undefined") {
|
||||||
// If we're relying on XDomainRequest, we
|
return options.ssl;
|
||||||
// must adhere to the page's encryption scheme.
|
}
|
||||||
return window.location.protocol === "https:" ? true : false;
|
// If we're relying on XDomainRequest, we
|
||||||
}();
|
// must adhere to the page's encryption scheme.
|
||||||
/** @type {string} */
|
return window.location.protocol === "https:" ? true : false;
|
||||||
var _endpoint;
|
})();
|
||||||
if (window.LEENDPOINT) {
|
/** @type {string} */
|
||||||
_endpoint = window.LEENDPOINT;
|
var _endpoint;
|
||||||
} else if (_noFormat) {
|
if (window.LEENDPOINT) {
|
||||||
_endpoint = "webhook.logentries.com/noformat";
|
_endpoint = window.LEENDPOINT;
|
||||||
}
|
} else if (_noFormat) {
|
||||||
else {
|
_endpoint = "webhook.logentries.com/noformat";
|
||||||
_endpoint = "js.logentries.com/v1";
|
} else {
|
||||||
}
|
_endpoint = "js.logentries.com/v1";
|
||||||
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
|
}
|
||||||
|
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to prevent further invocations on network err
|
* Flag to prevent further invocations on network err
|
||||||
** @type {boolean} */
|
** @type {boolean} */
|
||||||
var _shouldCall = true;
|
var _shouldCall = true;
|
||||||
/** @type {Array.<string>} */
|
/** @type {Array.<string>} */
|
||||||
var _backlog = [];
|
var _backlog = [];
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
var _active = false;
|
var _active = false;
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
var _sentPageInfo = false;
|
var _sentPageInfo = false;
|
||||||
|
|
||||||
var _apiCall = function(token, data) {
|
var _apiCall = function(token, data) {
|
||||||
_active = true;
|
_active = true;
|
||||||
|
|
||||||
var request = _getAjaxObject();
|
var request = _getAjaxObject();
|
||||||
|
|
||||||
if (_shouldCall) {
|
if (_shouldCall) {
|
||||||
if (request.constructor === XMLHttpRequest) {
|
if (request.constructor === XMLHttpRequest) {
|
||||||
// Currently we don't support fine-grained error
|
// Currently we don't support fine-grained error
|
||||||
// handling in older versions of IE
|
// handling in older versions of IE
|
||||||
request.onreadystatechange = function() {
|
request.onreadystatechange = function() {
|
||||||
if (request.readyState === 4) {
|
if (request.readyState === 4) {
|
||||||
// Handle any errors
|
// Handle any errors
|
||||||
if (request.status >= 400) {
|
if (request.status >= 400) {
|
||||||
console.error("Couldn't submit events.");
|
console.error("Couldn't submit events.");
|
||||||
if (request.status === 410) {
|
if (request.status === 410) {
|
||||||
// This API version has been phased out
|
// This API version has been phased out
|
||||||
console.warn("This version of le_js is no longer supported!");
|
console.warn("This version of le_js is no longer supported!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (request.status === 301) {
|
if (request.status === 301) {
|
||||||
// Server issued a deprecation warning
|
// Server issued a deprecation warning
|
||||||
console.warn("This version of le_js is deprecated! Consider upgrading.");
|
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());
|
if (_backlog.length > 0) {
|
||||||
} else {
|
// Submit the next event in the backlog
|
||||||
_active = false;
|
_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);
|
||||||
} else {
|
if (request.constructor === XMLHttpRequest) {
|
||||||
request.onload = function() {
|
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||||
if (_backlog.length > 0) {
|
request.setRequestHeader("Content-type", "application/json");
|
||||||
// Submit the next event in the backlog
|
}
|
||||||
_apiCall(token, _backlog.shift());
|
|
||||||
} else {
|
|
||||||
_active = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
request.open("POST", _endpoint, true);
|
if (request.overrideMimeType) {
|
||||||
if (request.constructor === XMLHttpRequest) {
|
request.overrideMimeType("text");
|
||||||
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
}
|
||||||
request.setRequestHeader('Content-type', 'application/json');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.overrideMimeType) {
|
request.send(data);
|
||||||
request.overrideMimeType('text');
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
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 _agentInfo = function() {
|
||||||
var raw = null;
|
var nav = window.navigator || { doNotTrack: undefined };
|
||||||
var args = Array.prototype.slice.call(arguments);
|
var screen = window.screen || {};
|
||||||
if (args.length === 0) {
|
var location = window.location || {};
|
||||||
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() {
|
return {
|
||||||
var nav = window.navigator || {doNotTrack: undefined};
|
url: location.pathname,
|
||||||
var screen = window.screen || {};
|
referrer: document.referrer,
|
||||||
var location = window.location || {};
|
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 {
|
// Single arg stops the compiler arity warning
|
||||||
url: location.pathname,
|
var _rawLog = function(msg) {
|
||||||
referrer: document.referrer,
|
var event = _getEvent.apply(this, arguments);
|
||||||
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 data = { event: event };
|
||||||
var _rawLog = function(msg) {
|
|
||||||
var event = _getEvent.apply(this, arguments);
|
|
||||||
|
|
||||||
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 (_traceCode) {
|
||||||
if (_pageInfo !== 'never') {
|
data.trace = _traceCode;
|
||||||
if (!_sentPageInfo || _pageInfo === 'per-entry') {
|
}
|
||||||
_sentPageInfo = true;
|
|
||||||
if (typeof event.screen === "undefined" &&
|
|
||||||
typeof event.browser === "undefined")
|
|
||||||
_rawLog(_agentInfo()).level('PAGE').send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_traceCode) {
|
return {
|
||||||
data.trace = _traceCode;
|
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) {
|
return {
|
||||||
// Don't log PAGE events to console
|
send: function() {
|
||||||
// PAGE events are generated for the agentInfo function
|
var cache = [];
|
||||||
if (_print && typeof console !== "undefined" && l !== 'PAGE') {
|
var serialized = JSON.stringify(data, function(key, value) {
|
||||||
var serialized = null;
|
if (typeof value === "undefined") {
|
||||||
if (typeof XDomainRequest !== "undefined") {
|
return "undefined";
|
||||||
// We're using IE8/9
|
} else if (typeof value === "object" && value !== null) {
|
||||||
serialized = data.trace + ' ' + data.event;
|
if (_indexOf(cache, value) !== -1) {
|
||||||
}
|
// We've seen this object before;
|
||||||
try {
|
// return a placeholder instead to prevent
|
||||||
console[l.toLowerCase()].call(console, (serialized || data));
|
// cycles
|
||||||
} catch (ex) {
|
return "<?>";
|
||||||
// IE compat fix
|
}
|
||||||
console.log((serialized || data));
|
cache.push(value);
|
||||||
}
|
}
|
||||||
}
|
return value;
|
||||||
data.level = l;
|
});
|
||||||
|
|
||||||
return {send: function() {
|
if (_active) {
|
||||||
var cache = [];
|
_backlog.push(serialized);
|
||||||
var serialized = JSON.stringify(data, function(key, value) {
|
} else {
|
||||||
|
_apiCall(_token, serialized);
|
||||||
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 (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
|
* A single log object
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
function Logger(options) {
|
function Logger(options) {
|
||||||
var logger;
|
var logger;
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
var dict = {
|
var dict = {
|
||||||
ssl: true,
|
ssl: true,
|
||||||
catchall: false,
|
catchall: false,
|
||||||
trace: true,
|
trace: true,
|
||||||
page_info: 'never',
|
page_info: "never",
|
||||||
print: false,
|
print: false,
|
||||||
endpoint: null,
|
endpoint: null,
|
||||||
token: null
|
token: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof options === "object")
|
if (typeof options === "object") for (var k in options) dict[k] = options[k];
|
||||||
for (var k in options)
|
else throw new Error("Invalid parameters for createLogStream()");
|
||||||
dict[k] = options[k];
|
|
||||||
else
|
|
||||||
throw new Error("Invalid parameters for createLogStream()");
|
|
||||||
|
|
||||||
if (dict.token === null) {
|
if (dict.token === null) {
|
||||||
throw new Error("Token not present.");
|
throw new Error("Token not present.");
|
||||||
} else {
|
} else {
|
||||||
logger = new LogStream(dict);
|
logger = new LogStream(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
var _log = function(msg) {
|
var _log = function(msg) {
|
||||||
if (logger) {
|
if (logger) {
|
||||||
return logger.log.apply(this, arguments);
|
return logger.log.apply(this, arguments);
|
||||||
} else
|
} else throw new Error("You must call LE.init(...) first.");
|
||||||
throw new Error("You must call LE.init(...) first.");
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// The public interface
|
// The public interface
|
||||||
return {
|
return {
|
||||||
log: function() {
|
log: function() {
|
||||||
_log.apply(this, arguments).level('LOG').send();
|
_log
|
||||||
},
|
.apply(this, arguments)
|
||||||
warn: function() {
|
.level("LOG")
|
||||||
_log.apply(this, arguments).level('WARN').send();
|
.send();
|
||||||
},
|
},
|
||||||
error: function() {
|
warn: function() {
|
||||||
_log.apply(this, arguments).level('ERROR').send();
|
_log
|
||||||
},
|
.apply(this, arguments)
|
||||||
info: function() {
|
.level("WARN")
|
||||||
_log.apply(this, arguments).level('INFO').send();
|
.send();
|
||||||
}
|
},
|
||||||
};
|
error: function() {
|
||||||
|
_log
|
||||||
|
.apply(this, arguments)
|
||||||
|
.level("ERROR")
|
||||||
|
.send();
|
||||||
|
},
|
||||||
|
info: function() {
|
||||||
|
_log
|
||||||
|
.apply(this, arguments)
|
||||||
|
.level("INFO")
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array of Logger elements
|
// Array of Logger elements
|
||||||
var loggers = {};
|
var loggers = {};
|
||||||
|
|
||||||
var _getLogger = function(name) {
|
var _getLogger = function(name) {
|
||||||
if (!loggers.hasOwnProperty(name))
|
if (!loggers.hasOwnProperty(name))
|
||||||
throw new Error("Invalid name for logStream");
|
throw new Error("Invalid name for logStream");
|
||||||
|
|
||||||
return loggers[name];
|
return loggers[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
var _createLogStream = function(options) {
|
var _createLogStream = function(options) {
|
||||||
if (typeof options.name !== "string")
|
if (typeof options.name !== "string") throw new Error("Name not present.");
|
||||||
throw new Error("Name not present.");
|
else if (loggers.hasOwnProperty(options.name))
|
||||||
else if (loggers.hasOwnProperty(options.name))
|
throw new Error("A logger with that name already exists!");
|
||||||
throw new Error("A logger with that name already exists!");
|
loggers[options.name] = new Logger(options);
|
||||||
loggers[options.name] = new Logger(options);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
var _deprecatedInit = function(options) {
|
var _deprecatedInit = function(options) {
|
||||||
var dict = {
|
var dict = {
|
||||||
name : "default"
|
name: "default"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof options === "object")
|
if (typeof options === "object") for (var k in options) dict[k] = options[k];
|
||||||
for (var k in options)
|
else if (typeof options === "string") dict.token = options;
|
||||||
dict[k] = options[k];
|
else throw new Error("Invalid parameters for init()");
|
||||||
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) {
|
var _destroyLogStream = function(name) {
|
||||||
if (typeof name === 'undefined'){
|
if (typeof name === "undefined") {
|
||||||
name = 'default';
|
name = "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
delete loggers[name];
|
delete loggers[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
// The public interface
|
// The public interface
|
||||||
export default {
|
export default {
|
||||||
init: _deprecatedInit,
|
init: _deprecatedInit,
|
||||||
createLogStream: _createLogStream,
|
createLogStream: _createLogStream,
|
||||||
to: _getLogger,
|
to: _getLogger,
|
||||||
destroy: _destroyLogStream,
|
destroy: _destroyLogStream,
|
||||||
log: function() {
|
log: function() {
|
||||||
for (var k in loggers)
|
for (var k in loggers) loggers[k].log.apply(this, arguments);
|
||||||
loggers[k].log.apply(this, arguments);
|
},
|
||||||
},
|
warn: function() {
|
||||||
warn: function() {
|
for (var k in loggers) loggers[k].warn.apply(this, arguments);
|
||||||
for (var k in loggers)
|
},
|
||||||
loggers[k].warn.apply(this, arguments);
|
error: function() {
|
||||||
},
|
for (var k in loggers) loggers[k].error.apply(this, arguments);
|
||||||
error: function() {
|
},
|
||||||
for (var k in loggers)
|
info: function() {
|
||||||
loggers[k].error.apply(this, arguments);
|
for (var k in loggers) loggers[k].info.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() {
|
function init() {
|
||||||
if(process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === "production") {
|
||||||
const opts = {};
|
const opts = {};
|
||||||
|
|
||||||
if(typeof __webpack_hash__ !== 'undefined') {
|
if (typeof __webpack_hash__ !== "undefined") {
|
||||||
opts['release'] = __webpack_hash__; // eslint-disable-line no-undef
|
opts["release"] = __webpack_hash__; // eslint-disable-line no-undef
|
||||||
}
|
|
||||||
|
|
||||||
Raven
|
|
||||||
.config('https://50006e5d91894f508dd288bbbf4585a6@sentry.io/185303', opts)
|
|
||||||
.install();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* slice until the end of the array.
|
||||||
* @return {bitArray} The requested slice.
|
* @return {bitArray} The requested slice.
|
||||||
*/
|
*/
|
||||||
bitSlice: function (a, bstart, bend) {
|
bitSlice: function(a, bstart, bend) {
|
||||||
a = bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
|
a = bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
|
||||||
return (bend === undefined) ? a : bitArray.clamp(a, bend-bstart);
|
return bend === undefined ? a : bitArray.clamp(a, bend - bstart);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,15 +23,17 @@ const bitArray = {
|
||||||
extract: function(a, bstart, blength) {
|
extract: function(a, bstart, blength) {
|
||||||
// FIXME: this Math.floor is not necessary at all, but for some reason
|
// FIXME: this Math.floor is not necessary at all, but for some reason
|
||||||
// seems to suppress a bug in the Chromium JIT.
|
// seems to suppress a bug in the Chromium JIT.
|
||||||
var x, sh = Math.floor((-bstart-blength) & 31);
|
var x,
|
||||||
if ((bstart + blength - 1 ^ bstart) & -32) {
|
sh = Math.floor((-bstart - blength) & 31);
|
||||||
|
if (((bstart + blength - 1) ^ bstart) & -32) {
|
||||||
// it crosses a boundary
|
// 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 {
|
} else {
|
||||||
// within a single word
|
// 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.
|
* @param {bitArray} a2 The second array.
|
||||||
* @return {bitArray} The concatenation of a1 and a2.
|
* @return {bitArray} The concatenation of a1 and a2.
|
||||||
*/
|
*/
|
||||||
concat: function (a1, a2) {
|
concat: function(a1, a2) {
|
||||||
if (a1.length === 0 || a2.length === 0) {
|
if (a1.length === 0 || a2.length === 0) {
|
||||||
return a1.concat(a2);
|
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) {
|
if (shift === 32) {
|
||||||
return a1.concat(a2);
|
return a1.concat(a2);
|
||||||
} else {
|
} 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.
|
* @param {bitArray} a The array.
|
||||||
* @return {Number} The length of a, in bits.
|
* @return {Number} The length of a, in bits.
|
||||||
*/
|
*/
|
||||||
bitLength: function (a) {
|
bitLength: function(a) {
|
||||||
var l = a.length, x;
|
var l = a.length,
|
||||||
if (l === 0) { return 0; }
|
x;
|
||||||
|
if (l === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
x = a[l - 1];
|
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.
|
* @param {Number} len The length to truncate to, in bits.
|
||||||
* @return {bitArray} A new array, truncated to len bits.
|
* @return {bitArray} A new array, truncated to len bits.
|
||||||
*/
|
*/
|
||||||
clamp: function (a, len) {
|
clamp: function(a, len) {
|
||||||
if (a.length * 32 < len) { return a; }
|
if (a.length * 32 < len) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
a = a.slice(0, Math.ceil(len / 32));
|
a = a.slice(0, Math.ceil(len / 32));
|
||||||
var l = a.length;
|
var l = a.length;
|
||||||
len &= 31;
|
len &= 31;
|
||||||
if (l > 0 && len) {
|
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;
|
return a;
|
||||||
},
|
},
|
||||||
|
@ -89,9 +102,11 @@ const bitArray = {
|
||||||
* @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
|
* @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
|
||||||
* @return {Number} The partial word.
|
* @return {Number} The partial word.
|
||||||
*/
|
*/
|
||||||
partial: function (len, x, _end) {
|
partial: function(len, x, _end) {
|
||||||
if (len === 32) { return x; }
|
if (len === 32) {
|
||||||
return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
|
return x;
|
||||||
|
}
|
||||||
|
return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,8 +114,8 @@ const bitArray = {
|
||||||
* @param {Number} x The partial word.
|
* @param {Number} x The partial word.
|
||||||
* @return {Number} The number of bits used by the partial word.
|
* @return {Number} The number of bits used by the partial word.
|
||||||
*/
|
*/
|
||||||
getPartial: function (x) {
|
getPartial: function(x) {
|
||||||
return Math.round(x/0x10000000000) || 32;
|
return Math.round(x / 0x10000000000) || 32;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,15 +124,16 @@ const bitArray = {
|
||||||
* @param {bitArray} b The second array.
|
* @param {bitArray} b The second array.
|
||||||
* @return {boolean} true if a == b; false otherwise.
|
* @return {boolean} true if a == b; false otherwise.
|
||||||
*/
|
*/
|
||||||
equal: function (a, b) {
|
equal: function(a, b) {
|
||||||
if (bitArray.bitLength(a) !== bitArray.bitLength(b)) {
|
if (bitArray.bitLength(a) !== bitArray.bitLength(b)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var x = 0, i;
|
var x = 0,
|
||||||
for (i=0; i<a.length; i++) {
|
i;
|
||||||
x |= a[i]^b[i];
|
for (i = 0; i < a.length; i++) {
|
||||||
|
x |= a[i] ^ b[i];
|
||||||
}
|
}
|
||||||
return (x === 0);
|
return x === 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Shift an array right.
|
/** Shift an array right.
|
||||||
|
@ -127,9 +143,13 @@ const bitArray = {
|
||||||
* @param {bitArray} [out=[]] An array to prepend to the output.
|
* @param {bitArray} [out=[]] An array to prepend to the output.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_shiftRight: function (a, shift, carry, out) {
|
_shiftRight: function(a, shift, carry, out) {
|
||||||
var i, last2=0, shift2;
|
var i,
|
||||||
if (out === undefined) { out = []; }
|
last2 = 0,
|
||||||
|
shift2;
|
||||||
|
if (out === undefined) {
|
||||||
|
out = [];
|
||||||
|
}
|
||||||
|
|
||||||
for (; shift >= 32; shift -= 32) {
|
for (; shift >= 32; shift -= 32) {
|
||||||
out.push(carry);
|
out.push(carry);
|
||||||
|
@ -139,21 +159,27 @@ const bitArray = {
|
||||||
return out.concat(a);
|
return out.concat(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i=0; i<a.length; i++) {
|
for (i = 0; i < a.length; i++) {
|
||||||
out.push(carry | (a[i]>>>shift));
|
out.push(carry | (a[i] >>> shift));
|
||||||
carry = a[i] << (32-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);
|
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;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** xor a block of 4 words together.
|
/** xor a block of 4 words together.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_xor4: function(x,y) {
|
_xor4: function(x, y) {
|
||||||
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
|
return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
|
||||||
},
|
},
|
||||||
|
|
||||||
/** byteswap a word array inplace.
|
/** byteswap a word array inplace.
|
||||||
|
@ -162,7 +188,9 @@ const bitArray = {
|
||||||
* @return {bitArray} byteswapped array
|
* @return {bitArray} byteswapped array
|
||||||
*/
|
*/
|
||||||
byteswapM: function(a) {
|
byteswapM: function(a) {
|
||||||
var i, v, m = 0xff00;
|
var i,
|
||||||
|
v,
|
||||||
|
m = 0xff00;
|
||||||
for (i = 0; i < a.length; ++i) {
|
for (i = 0; i < a.length; ++i) {
|
||||||
v = a[i];
|
v = a[i];
|
||||||
a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
|
a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
|
||||||
|
@ -173,18 +201,20 @@ const bitArray = {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fromBytes: function(bytes) {
|
fromBytes: function(bytes) {
|
||||||
var out = [], i, tmp=0;
|
var out = [],
|
||||||
for (i=0; i<bytes.length; i++) {
|
i,
|
||||||
|
tmp = 0;
|
||||||
|
for (i = 0; i < bytes.length; i++) {
|
||||||
tmp = (tmp << 8) | bytes[i];
|
tmp = (tmp << 8) | bytes[i];
|
||||||
if ((i&3) === 3) {
|
if ((i & 3) === 3) {
|
||||||
out.push(tmp);
|
out.push(tmp);
|
||||||
tmp = 0;
|
tmp = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i&3) {
|
if (i & 3) {
|
||||||
out.push(bitArray.partial(8*(i&3), tmp));
|
out.push(bitArray.partial(8 * (i & 3), tmp));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
...bitArray
|
...bitArray
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
const Uint64BE = require('int64-buffer').Uint64BE
|
const Uint64BE = require("int64-buffer").Uint64BE;
|
||||||
|
|
||||||
export function formatForMsg(msg) {
|
export function formatForMsg(msg) {
|
||||||
return {bstart: 0, bend: 15}
|
return { bstart: 0, bend: 15 };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMsgDec(msg) {
|
export function formatMsgDec(msg) {
|
||||||
const {bstart, bend} = formatForMsg(msg)
|
const { bstart, bend } = formatForMsg(msg);
|
||||||
const uint = Uint64BE(msg[1])
|
const uint = Uint64BE(msg[1]);
|
||||||
var tt = "0"+uint.toString(2);
|
var tt = "0" + uint.toString(2);
|
||||||
tt = tt.substring(0, tt.length - (63-bend))
|
tt = tt.substring(0, tt.length - (63 - bend));
|
||||||
tt = tt.substring(tt.length - (bend-bstart) - 1)
|
tt = tt.substring(tt.length - (bend - bstart) - 1);
|
||||||
return [msg[0], parseInt(tt, 2)];
|
return [msg[0], parseInt(tt, 2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,17 +20,17 @@ export function uint64BEToHex(int64) {
|
||||||
export function int64BufferToPrettyHexStr(buffer) {
|
export function int64BufferToPrettyHexStr(buffer) {
|
||||||
const uint = Uint64BE(buffer);
|
const uint = Uint64BE(buffer);
|
||||||
let hex = uint.toString(16);
|
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);
|
let hexParts = hex.match(/.{1,2}/g);
|
||||||
|
|
||||||
return hexParts.join(" ")
|
return hexParts.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMsgHex(msg) {
|
export function formatMsgHex(msg) {
|
||||||
const uint = Uint64BE(msg[1]);
|
const uint = Uint64BE(msg[1]);
|
||||||
let hex = uint.toString(16);
|
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);
|
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 {
|
export default class BoardUnit {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
this.comment = null
|
this.comment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
text() {
|
text() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -421,12 +421,9 @@ export default class DBC {
|
||||||
messageId = parseInt(messageId, 10);
|
messageId = parseInt(messageId, 10);
|
||||||
const msg = messages.get(messageId);
|
const msg = messages.get(messageId);
|
||||||
if (msg === undefined) {
|
if (msg === undefined) {
|
||||||
warnings.push(`failed to parse signal comment on line ${i + 1} -- ${
|
warnings.push(`failed to parse signal comment on line ${i +
|
||||||
line
|
1} -- ${line}:
|
||||||
}:
|
message id ${messageId} does not exist prior to this line`);
|
||||||
message id ${
|
|
||||||
messageId
|
|
||||||
} does not exist prior to this line`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const signal = msg.signals[signalName];
|
const signal = msg.signals[signalName];
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
function findTimeIndex(entries, time) {
|
function findTimeIndex(entries, time) {
|
||||||
return entries.findIndex((e) => e.time >= time);
|
return entries.findIndex(e => e.time >= time);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findRelativeTimeIndex(entries, relTime) {
|
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
|
Finds pair of indices (inclusive, exclusive) within entries array
|
||||||
whose timestamps match segmentTimeLow and segmentTimeHi.
|
whose timestamps match segmentTimeLow and segmentTimeHi.
|
||||||
if isRelative === true, then the segment times
|
if isRelative === true, then the segment times
|
||||||
|
@ -16,15 +20,19 @@ function findSegmentIndices(entries, [segmentTimeLow, segmentTimeHi], isRelative
|
||||||
Returns `[segmentIdxLow, segmentIdxHigh]`
|
Returns `[segmentIdxLow, segmentIdxHigh]`
|
||||||
(inclusive, exclusive)
|
(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);
|
const upperSegments = entries.slice(segmentIdxLow);
|
||||||
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
|
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
|
||||||
const segmentIdxHi = (upperSegmentIdxHi >= 0 ? upperSegmentIdxHi + segmentIdxLow + 1 : entries.length - 1)
|
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 {
|
export default class Frame {
|
||||||
constructor({name,
|
constructor({
|
||||||
id = 0,
|
name,
|
||||||
size = 0,
|
id = 0,
|
||||||
transmitters = [],
|
size = 0,
|
||||||
extended = 0,
|
transmitters = [],
|
||||||
comment = null,
|
extended = 0,
|
||||||
signals = {}}) {
|
comment = null,
|
||||||
Object.assign(this, {name,
|
signals = {}
|
||||||
id,
|
}) {
|
||||||
size,
|
Object.assign(this, {
|
||||||
transmitters,
|
name,
|
||||||
extended,
|
id,
|
||||||
comment,
|
size,
|
||||||
signals})
|
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() {
|
copy() {
|
||||||
let txNum = 1, txName;
|
const copy = Object.assign(Object.create(this), this);
|
||||||
do {
|
|
||||||
txName = 'NEW_TRANSMITTER_' + txNum;
|
|
||||||
txNum++;
|
|
||||||
} while(this.transmitters.indexOf(txName) !== -1);
|
|
||||||
|
|
||||||
return txName;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +1,173 @@
|
||||||
import ArrayUtils from '../utils/array';
|
import ArrayUtils from "../utils/array";
|
||||||
import {CAN_GRAPH_MAX_POINTS} from '../config';
|
import { CAN_GRAPH_MAX_POINTS } from "../config";
|
||||||
|
|
||||||
function _calcGraphData(msg, signalUid, firstCanTime) {
|
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);
|
const signal = Object.values(msg.frame.signals).find(
|
||||||
if(!signal) {
|
s => s.uid === signalUid
|
||||||
console.warn('_calcGraphData: no signal', signalUid, msg)
|
);
|
||||||
return null;
|
if (!signal) {
|
||||||
}
|
console.warn("_calcGraphData: no signal", signalUid, msg);
|
||||||
let samples = [];
|
return null;
|
||||||
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
|
}
|
||||||
|
let samples = [];
|
||||||
|
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
|
||||||
|
|
||||||
if(skip === 0){
|
if (skip === 0) {
|
||||||
samples = msg.entries;
|
samples = msg.entries;
|
||||||
} else {
|
} else {
|
||||||
for(let i = 0; i < msg.entries.length; i += skip) {
|
for (let i = 0; i < msg.entries.length; i += skip) {
|
||||||
samples.push(msg.entries[i]);
|
samples.push(msg.entries[i]);
|
||||||
}
|
|
||||||
// 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)
|
// Always include last message entry, which faciliates graphData comparison
|
||||||
.map((entry) => {
|
samples.push(msg.entries[msg.entries.length - 1]);
|
||||||
return {x: entry.time,
|
}
|
||||||
relTime: entry.relTime,
|
return samples
|
||||||
y: entry.signals[signal.name],
|
.filter(e => e.signals[signal.name] !== undefined)
|
||||||
unit: signal.unit,
|
.map(entry => {
|
||||||
color: `rgba(${signal.colors.join(",")}, 0.5)`,
|
return {
|
||||||
signalName: signal.name,
|
x: entry.time,
|
||||||
signalUid}
|
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) {
|
function appendNewGraphData(plottedSignals, graphData, messages, firstCanTime) {
|
||||||
const messagesPerPlot = plottedSignals.map((plottedMessages) =>
|
const messagesPerPlot = plottedSignals.map(plottedMessages =>
|
||||||
plottedMessages.reduce((messages,
|
plottedMessages.reduce((messages, { messageId, signalUid }) => {
|
||||||
{messageId, signalUid}) => {
|
messages.push(messageId);
|
||||||
messages.push(messageId);
|
return messages;
|
||||||
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
|
const newEntries = entries.slice(firstNewEntryIdx);
|
||||||
.map((plottedMessageIds, index) => {return {plottedMessageIds, index}}) // preserve index so we can look up graphData
|
signalUidsByMessageId[messageId].forEach(signalUid => {
|
||||||
.filter(({plottedMessageIds, index}) => {
|
const signalGraphData = _calcGraphData(
|
||||||
if(index < graphData.length) {
|
{
|
||||||
let maxGraphTime = 0;
|
...messages[messageId],
|
||||||
const { series } = graphData[index];
|
entries: newEntries
|
||||||
if(series.length > 0) {
|
},
|
||||||
maxGraphTime = series[graphData[index].series.length - 1].relTime;
|
signalUid,
|
||||||
}
|
firstCanTime
|
||||||
|
);
|
||||||
|
|
||||||
return plottedMessageIds.some((messageId) =>
|
newGraphData = newGraphData.concat(signalGraphData);
|
||||||
(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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const messageIdOutOfBounds = (
|
const messageIdOutOfBounds =
|
||||||
series.length > 0
|
series.length > 0 &&
|
||||||
&& plottedMessageIds.find((messageId) =>
|
plottedMessageIds.find(
|
||||||
messages[messageId].entries.length > 0
|
messageId =>
|
||||||
&& series[0].relTime < messages[messageId].entries[0].relTime));
|
messages[messageId].entries.length > 0 &&
|
||||||
graphData[index] = {
|
series[0].relTime < messages[messageId].entries[0].relTime
|
||||||
series: graphData[index].series.concat(newGraphData),
|
);
|
||||||
updated: Date.now()
|
graphData[index] = {
|
||||||
};
|
series: graphData[index].series.concat(newGraphData),
|
||||||
|
updated: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
if(messageIdOutOfBounds) {
|
if (messageIdOutOfBounds) {
|
||||||
const graphDataLowerBound = graphData[index].series.findIndex(
|
const graphDataLowerBound = graphData[index].series.findIndex(
|
||||||
(e) => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime);
|
e => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime
|
||||||
|
);
|
||||||
|
|
||||||
if(graphDataLowerBound) {
|
if (graphDataLowerBound) {
|
||||||
graphData[index].series = graphData[index].series.slice(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 React from "react";
|
||||||
import {StyleSheet, css} from 'aphrodite/no-important';
|
import { StyleSheet, css } from "aphrodite/no-important";
|
||||||
|
|
||||||
import * as ObjectUtils from '../utils/object';
|
import * as ObjectUtils from "../utils/object";
|
||||||
|
|
||||||
function createImageComponent(source, alt, styles) {
|
function createImageComponent(source, alt, styles) {
|
||||||
if(styles === undefined) {
|
if (styles === undefined) {
|
||||||
styles = []
|
styles = [];
|
||||||
} else if(!Array.isArray(styles)) {
|
} else if (!Array.isArray(styles)) {
|
||||||
styles = [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) => {
|
return (
|
||||||
let localStyles = styles.slice();
|
<img src={source} className={css(...localStyles)} alt={alt} {...props} />
|
||||||
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} />
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Styles = StyleSheet.create({
|
const Styles = StyleSheet.create({
|
||||||
materialIcon: {
|
materialIcon: {
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24
|
||||||
},
|
},
|
||||||
pointer: {
|
pointer: {
|
||||||
cursor: 'pointer'
|
cursor: "pointer"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
|
const leftArrow = createImageComponent(
|
||||||
'Left arrow',
|
process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
|
||||||
Styles.materialIcon);
|
"Left arrow",
|
||||||
const rightArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_right_black_24dp.png",
|
Styles.materialIcon
|
||||||
'Right 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",
|
const downArrow = createImageComponent(
|
||||||
'Down arrow',
|
process.env.PUBLIC_URL + "/img/ic_arrow_drop_down_black_24dp.png",
|
||||||
Styles.materialIcon)
|
"Down arrow",
|
||||||
|
Styles.materialIcon
|
||||||
|
);
|
||||||
|
|
||||||
const clear = createImageComponent(process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
|
const clear = createImageComponent(
|
||||||
'Clear',
|
process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
|
||||||
[Styles.materialIcon, Styles.pointer]);
|
"Clear",
|
||||||
|
[Styles.materialIcon, Styles.pointer]
|
||||||
|
);
|
||||||
|
|
||||||
const panda = createImageComponent(process.env.PUBLIC_URL + "/img/panda.png", 'Panda', []);
|
const panda = createImageComponent(
|
||||||
export default {rightArrow, leftArrow, downArrow, clear, panda};
|
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({
|
export default StyleSheet.create({
|
||||||
tab: {
|
tab: {
|
||||||
display: 'inline',
|
display: "inline",
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
cursor: 'pointer'
|
cursor: "pointer"
|
||||||
},
|
},
|
||||||
selectedTab: {
|
selectedTab: {
|
||||||
borderBottom: '2px solid #000',
|
borderBottom: "2px solid #000",
|
||||||
fontWeight: 'bold'
|
fontWeight: "bold"
|
||||||
},
|
},
|
||||||
tabContent: {
|
tabContent: {
|
||||||
paddingTop: 20
|
paddingTop: 20
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import {StyleSheet} from 'aphrodite/no-important';
|
import { StyleSheet } from "aphrodite/no-important";
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
backgroundColor: 'RGB(63, 135, 255)',
|
backgroundColor: "RGB(63, 135, 255)",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
paddingLeft: 20,
|
paddingLeft: 20,
|
||||||
paddingRight: 20,
|
paddingRight: 20,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
color: 'white',
|
color: "white",
|
||||||
':hover': {
|
":hover": {
|
||||||
backgroundColor: 'RGBA(63, 135, 255, 0.5)'
|
backgroundColor: "RGBA(63, 135, 255, 0.5)"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
function elementWiseEquals(arr1, arr2) {
|
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) {
|
function findIndexRight(arr, condition) {
|
||||||
for(let i = arr.length - 1; i >= 0; i--) {
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
if(condition(arr[i])) {
|
if (condition(arr[i])) {
|
||||||
return i;
|
return i;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {elementWiseEquals, findIndexRight};
|
export default { elementWiseEquals, findIndexRight };
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
export function shade([r, g, b], opacity) {
|
export function shade([r, g, b], opacity) {
|
||||||
/*
|
/*
|
||||||
@param {Array} rgb
|
@param {Array} rgb
|
||||||
@param {Number} opacity -1.0 -> +1.0
|
@param {Number} opacity -1.0 -> +1.0
|
||||||
negative opacity will darken image
|
negative opacity will darken image
|
||||||
|
|
||||||
returns new RGB array
|
returns new RGB array
|
||||||
*/
|
*/
|
||||||
const t = (opacity < 0) ? 0 : 255;
|
const t = opacity < 0 ? 0 : 255;
|
||||||
const opacityPos = Math.abs(opacity);
|
const opacityPos = Math.abs(opacity);
|
||||||
|
|
||||||
return [Math.round((t - r) * opacityPos) + r,
|
return [
|
||||||
Math.round((t - g) * opacityPos) + g,
|
Math.round((t - r) * opacityPos) + r,
|
||||||
Math.round((t - b) * opacityPos) + b];
|
Math.round((t - g) * opacityPos) + g,
|
||||||
}
|
Math.round((t - b) * opacityPos) + b
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -4,19 +4,17 @@
|
||||||
|
|
||||||
// Useful for component testing
|
// Useful for component testing
|
||||||
|
|
||||||
import { css } from 'aphrodite';
|
import { css } from "aphrodite";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const styles = [];
|
const styles = [];
|
||||||
const classes = [];
|
const classes = [];
|
||||||
|
|
||||||
[].forEach.call(arguments, it => {
|
[].forEach.call(arguments, it => {
|
||||||
if (it && it._definition && it._name)
|
if (it && it._definition && it._name) styles.push(it);
|
||||||
styles.push(it);
|
else classes.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) {
|
function findMaxByteStateChangeCount(messages) {
|
||||||
return Object.values(messages).map((m) => m.byteStateChangeCounts)
|
return Object.values(messages)
|
||||||
.reduce((counts, countArr) => counts.concat(countArr), []) // flatten arrays
|
.map(m => m.byteStateChangeCounts)
|
||||||
.reduce((count1, count2) => count1 > count2 ? count1 : count2, 0); // find max
|
.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) {
|
function addCanMessage(
|
||||||
var id = source + ":" + address.toString(16);
|
[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 ?
|
const prevMsgEntry =
|
||||||
messages[id].entries[messages[id].entries.length - 1]
|
messages[id].entries.length > 0
|
||||||
:
|
? messages[id].entries[messages[id].entries.length - 1]
|
||||||
(prevMsgEntries[id] || null);
|
: prevMsgEntries[id] || null;
|
||||||
|
|
||||||
if(byteStateChangeCountsByMessage[id] && messages[id].byteStateChangeCounts.every((c) => c === 0)) {
|
if (
|
||||||
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id]
|
byteStateChangeCountsByMessage[id] &&
|
||||||
}
|
messages[id].byteStateChangeCounts.every(c => c === 0)
|
||||||
|
) {
|
||||||
|
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id];
|
||||||
|
}
|
||||||
|
|
||||||
const {msgEntry,
|
const { msgEntry, byteStateChangeCounts } = parseMessage(
|
||||||
byteStateChangeCounts} = parseMessage(dbc,
|
dbc,
|
||||||
busTime,
|
busTime,
|
||||||
address,
|
address,
|
||||||
data,
|
data,
|
||||||
canStartTime,
|
canStartTime,
|
||||||
prevMsgEntry);
|
prevMsgEntry
|
||||||
|
);
|
||||||
|
|
||||||
messages[id].byteStateChangeCounts = byteStateChangeCounts.map((count, idx) =>
|
messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
|
||||||
messages[id].byteStateChangeCounts[idx] + count
|
(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) {
|
function createMessageSpec(dbc, address, id, bus) {
|
||||||
const frame = dbc.messages.get(address);
|
const frame = dbc.messages.get(address);
|
||||||
const size = frame ? frame.size : 8;
|
const size = frame ? frame.size : 8;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: address,
|
address: address,
|
||||||
id: id,
|
id: id,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
entries: [],
|
entries: [],
|
||||||
frame: frame,
|
frame: frame,
|
||||||
byteColors: Array(size).fill(0),
|
byteColors: Array(size).fill(0),
|
||||||
byteStateChangeCounts: Array(size).fill(0)
|
byteStateChangeCounts: Array(size).fill(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function determineByteStateChangeTimes(hexData, time, msgSize, lastParsedMessage) {
|
function determineByteStateChangeTimes(
|
||||||
const byteStateChangeCounts = Array(msgSize).fill(0);
|
hexData,
|
||||||
let byteStateChangeTimes;
|
time,
|
||||||
|
msgSize,
|
||||||
|
lastParsedMessage
|
||||||
|
) {
|
||||||
|
const byteStateChangeCounts = Array(msgSize).fill(0);
|
||||||
|
let byteStateChangeTimes;
|
||||||
|
|
||||||
if(!lastParsedMessage) {
|
if (!lastParsedMessage) {
|
||||||
byteStateChangeTimes = Array(msgSize).fill(time);
|
byteStateChangeTimes = Array(msgSize).fill(time);
|
||||||
} else {
|
} else {
|
||||||
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
|
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
|
||||||
|
|
||||||
for(let i = 0; i < byteStateChangeTimes.length; i++) {
|
for (let i = 0; i < byteStateChangeTimes.length; i++) {
|
||||||
const currentData = hexData.substr(i * 2, 2),
|
const currentData = hexData.substr(i * 2, 2),
|
||||||
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
|
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
|
||||||
|
|
||||||
if(currentData !== prevData) {
|
if (currentData !== prevData) {
|
||||||
byteStateChangeTimes[i] = time;
|
byteStateChangeTimes[i] = time;
|
||||||
byteStateChangeCounts[i] = 1;
|
byteStateChangeCounts[i] = 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {byteStateChangeTimes, byteStateChangeCounts};
|
return { byteStateChangeTimes, byteStateChangeCounts };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes) {
|
function createMessageEntry(
|
||||||
return {
|
dbc,
|
||||||
signals: dbc.getSignalValues(address, data),
|
address,
|
||||||
time,
|
time,
|
||||||
relTime,
|
relTime,
|
||||||
hexData: Buffer.from(data).toString('hex'),
|
data,
|
||||||
byteStateChangeTimes,
|
byteStateChangeTimes
|
||||||
updated: Date.now(),
|
) {
|
||||||
};
|
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) {
|
function parseMessage(dbc, time, address, data, timeStart, lastParsedMessage) {
|
||||||
let hexData;
|
let hexData;
|
||||||
if(typeof data === 'string') {
|
if (typeof data === "string") {
|
||||||
hexData = data;
|
hexData = data;
|
||||||
data = Buffer.from(data, 'hex');
|
data = Buffer.from(data, "hex");
|
||||||
} else {
|
} else {
|
||||||
hexData = Buffer.from(data).toString('hex');
|
hexData = Buffer.from(data).toString("hex");
|
||||||
}
|
}
|
||||||
const msgSpec = dbc.messages.get(address);
|
const msgSpec = dbc.messages.get(address);
|
||||||
const msgSize = msgSpec ? msgSpec.size : 8;
|
const msgSize = msgSpec ? msgSpec.size : 8;
|
||||||
const relTime = time - timeStart;
|
const relTime = time - timeStart;
|
||||||
|
|
||||||
const {byteStateChangeTimes,
|
const {
|
||||||
byteStateChangeCounts} = determineByteStateChangeTimes(hexData,
|
byteStateChangeTimes,
|
||||||
relTime,
|
byteStateChangeCounts
|
||||||
msgSize,
|
} = determineByteStateChangeTimes(
|
||||||
lastParsedMessage);
|
hexData,
|
||||||
const msgEntry = createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes);
|
relTime,
|
||||||
|
msgSize,
|
||||||
|
lastParsedMessage
|
||||||
|
);
|
||||||
|
const msgEntry = createMessageEntry(
|
||||||
|
dbc,
|
||||||
|
address,
|
||||||
|
time,
|
||||||
|
relTime,
|
||||||
|
data,
|
||||||
|
byteStateChangeTimes
|
||||||
|
);
|
||||||
|
|
||||||
return {msgEntry, byteStateChangeCounts};
|
return { msgEntry, byteStateChangeCounts };
|
||||||
}
|
}
|
||||||
|
|
||||||
const BIG_ENDIAN_START_BITS = [];
|
const BIG_ENDIAN_START_BITS = [];
|
||||||
for(let i = 0; i < 64; i += 8) {
|
for (let i = 0; i < 64; i += 8) {
|
||||||
for(let j = 7; j > -1; j--) {
|
for (let j = 7; j > -1; j--) {
|
||||||
BIG_ENDIAN_START_BITS.push(i + j);
|
BIG_ENDIAN_START_BITS.push(i + j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bigEndianBitIndex(matrixBitIndex) {
|
function bigEndianBitIndex(matrixBitIndex) {
|
||||||
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
|
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
function matrixBitNumber(bigEndianIndex) {
|
function matrixBitNumber(bigEndianIndex) {
|
||||||
return BIG_ENDIAN_START_BITS[bigEndianIndex];
|
return BIG_ENDIAN_START_BITS[bigEndianIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMessageByteColors(message, maxByteStateChangeCount) {
|
function setMessageByteColors(message, maxByteStateChangeCount) {
|
||||||
message.byteColors = message.byteStateChangeCounts.map((count) =>
|
message.byteColors = message.byteStateChangeCounts
|
||||||
isNaN(count) ? 0 : Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
|
.map(
|
||||||
).map((red) =>
|
count =>
|
||||||
'rgb(' + Math.round(red) + ',0,0)'
|
isNaN(count)
|
||||||
);
|
? 0
|
||||||
|
: Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
|
||||||
|
)
|
||||||
|
.map(red => "rgb(" + Math.round(red) + ",0,0)");
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {bigEndianBitIndex,
|
export default {
|
||||||
addCanMessage,
|
bigEndianBitIndex,
|
||||||
createMessageSpec,
|
addCanMessage,
|
||||||
matrixBitNumber,
|
createMessageSpec,
|
||||||
parseMessage,
|
matrixBitNumber,
|
||||||
findMaxByteStateChangeCount,
|
parseMessage,
|
||||||
setMessageByteColors,
|
findMaxByteStateChangeCount,
|
||||||
createMessageEntry};
|
setMessageByteColors,
|
||||||
|
createMessageEntry
|
||||||
|
};
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
export default function debounce(func, wait, immediate) {
|
export default function debounce(func, wait, immediate) {
|
||||||
var timeout;
|
var timeout;
|
||||||
return function() {
|
return function() {
|
||||||
var context = this, args = arguments;
|
var context = this,
|
||||||
var later = function() {
|
args = arguments;
|
||||||
timeout = null;
|
var later = function() {
|
||||||
if (!immediate) func.apply(context, args);
|
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 callNow = immediate && !timeout;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
if (callNow) func.apply(context, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,58 +1,67 @@
|
||||||
const binTimeIntervals = [
|
const binTimeIntervals = [
|
||||||
{seconds: 1,
|
{
|
||||||
title: 'second'},
|
seconds: 1,
|
||||||
{seconds: 60,
|
title: "second"
|
||||||
title: 'minute'},
|
},
|
||||||
{seconds: 300,
|
{
|
||||||
title: '5 minutes'},
|
seconds: 60,
|
||||||
{seconds: 3600,
|
title: "minute"
|
||||||
title: 'hour'}
|
},
|
||||||
|
{
|
||||||
|
seconds: 300,
|
||||||
|
title: "5 minutes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
seconds: 3600,
|
||||||
|
title: "hour"
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function prettyBinDuration(samplesDurationSeconds, maxBinCount = 100) {
|
function prettyBinDuration(samplesDurationSeconds, maxBinCount = 100) {
|
||||||
for(let i = 0; i < binTimeIntervals.length; i++) {
|
for (let i = 0; i < binTimeIntervals.length; i++) {
|
||||||
const interval = binTimeIntervals[i];
|
const interval = binTimeIntervals[i];
|
||||||
if(samplesDurationSeconds / interval.seconds <= maxBinCount) {
|
if (samplesDurationSeconds / interval.seconds <= maxBinCount) {
|
||||||
return interval;
|
return interval;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// maxBinCount is exceeded.
|
// maxBinCount is exceeded.
|
||||||
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
|
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
|
||||||
// Just going to use hours.
|
// Just going to use hours.
|
||||||
|
|
||||||
return binTimeIntervals[3];
|
return binTimeIntervals[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function binMessages(messageEntries, segmentIndices) {
|
export function binMessages(messageEntries, segmentIndices) {
|
||||||
let startIdx = 0, endIdx = messageEntries.length - 1;
|
let startIdx = 0,
|
||||||
if(segmentIndices && segmentIndices.length === 2) {
|
endIdx = messageEntries.length - 1;
|
||||||
[startIdx, endIdx] = segmentIndices;
|
if (segmentIndices && segmentIndices.length === 2) {
|
||||||
}
|
[startIdx, endIdx] = segmentIndices;
|
||||||
const first = messageEntries[startIdx], last = messageEntries[endIdx];
|
}
|
||||||
const binDuration = prettyBinDuration(last.time - first.time);
|
const first = messageEntries[startIdx],
|
||||||
|
last = messageEntries[endIdx];
|
||||||
|
const binDuration = prettyBinDuration(last.time - first.time);
|
||||||
|
|
||||||
const bins = [];
|
const bins = [];
|
||||||
const offset = first.time - first.relTime;
|
const offset = first.time - first.relTime;
|
||||||
|
|
||||||
for(let t = first.time; t < last.time; t += binDuration.seconds) {
|
for (let t = first.time; t < last.time; t += binDuration.seconds) {
|
||||||
const binCutoffTime = t + binDuration.seconds;
|
const binCutoffTime = t + binDuration.seconds;
|
||||||
const previousBinEnd = startIdx;
|
const previousBinEnd = startIdx;
|
||||||
|
|
||||||
let entry = {time: -1}
|
let entry = { time: -1 };
|
||||||
while(entry !== undefined && entry.time < binCutoffTime) {
|
while (entry !== undefined && entry.time < binCutoffTime) {
|
||||||
entry = messageEntries[startIdx];
|
entry = messageEntries[startIdx];
|
||||||
++startIdx;
|
++startIdx;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bins.push({
|
|
||||||
count: startIdx - previousBinEnd,
|
|
||||||
startTime: t,
|
|
||||||
endTime: binCutoffTime,
|
|
||||||
relStartTime: t - offset
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
|
||||||
|
|
||||||
const NumpyLoader = (function NumpyLoader() {
|
const NumpyLoader = (function NumpyLoader() {
|
||||||
function asciiDecode(buf) {
|
function asciiDecode(buf) {
|
||||||
return String.fromCharCode.apply(null, new Uint8Array(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 version = new Uint8Array(buf.slice(6, 8)),
|
||||||
var view = new DataView(buffer);
|
headerLength = readUint16LE(buf.slice(8, 10)),
|
||||||
var val = view.getUint8(0);
|
headerStr = asciiDecode(buf.slice(10, 10 + headerLength));
|
||||||
val |= view.getUint8(1) << 8;
|
const offsetBytes = 10 + headerLength;
|
||||||
return val;
|
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
|
||||||
}
|
|
||||||
|
|
||||||
function fromArrayBuffer(buf) {
|
// Hacky conversion of dict literal string to JS Object
|
||||||
// Check the magic number
|
const info = JSON.parse(
|
||||||
var magic = asciiDecode(buf.slice(0,6));
|
headerStr
|
||||||
if (magic !== "\x93NUMPY") {
|
.toLowerCase()
|
||||||
throw new Error("Bad magic number");
|
.replace("(", "[")
|
||||||
}
|
.replace("),", "]")
|
||||||
|
.replace(/'/g, '"')
|
||||||
|
.replace(",]", "]")
|
||||||
|
);
|
||||||
|
|
||||||
var version = new Uint8Array(buf.slice(6,8)),
|
// Intepret the bytes according to the specified dtype
|
||||||
headerLength = readUint16LE(buf.slice(8,10)),
|
var data;
|
||||||
headerStr = asciiDecode(buf.slice(10, 10+headerLength));
|
if (info.descr === "|u1") {
|
||||||
const offsetBytes = 10 + headerLength;
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
|
} else if (info.descr === "|i1") {
|
||||||
|
data = new Int8Array(buf, offsetBytes);
|
||||||
// Hacky conversion of dict literal string to JS Object
|
} else if (info.descr === "<u2") {
|
||||||
const info = JSON.parse(headerStr.toLowerCase()
|
data = new Uint16Array(buf, offsetBytes);
|
||||||
.replace('(','[')
|
} else if (info.descr === "<i2") {
|
||||||
.replace('),',']')
|
data = new Int16Array(buf, offsetBytes);
|
||||||
.replace(/'/g, '"')
|
} else if (info.descr === "<u4") {
|
||||||
.replace(',]', ']'));
|
data = new Uint32Array(buf, offsetBytes);
|
||||||
|
} else if (info.descr === "<i4") {
|
||||||
// Intepret the bytes according to the specified dtype
|
data = new Int32Array(buf, offsetBytes);
|
||||||
var data;
|
} else if (info.descr === "<f4") {
|
||||||
if (info.descr === "|u1") {
|
data = new Float32Array(buf, offsetBytes);
|
||||||
data = new Uint8Array(buf, offsetBytes);
|
} else if (info.descr === "<f8") {
|
||||||
} else if (info.descr === "|i1") {
|
data = new Float64Array(buf, offsetBytes);
|
||||||
data = new Int8Array(buf, offsetBytes);
|
} else if (info.descr === "<u8") {
|
||||||
} else if (info.descr === "<u2") {
|
// 8 byte uint64s
|
||||||
data = new Uint16Array(buf, offsetBytes);
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
} else if (info.descr === "<i2") {
|
} else if (info.descr === "<i8") {
|
||||||
data = new Int16Array(buf, offsetBytes);
|
// 8 byte int64s
|
||||||
} else if (info.descr === "<u4") {
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
data = new Uint32Array(buf, offsetBytes);
|
} else if (info.descr === "|s5") {
|
||||||
} else if (info.descr === "<i4") {
|
// 5 byte string
|
||||||
data = new Int32Array(buf, offsetBytes);
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
} else if (info.descr === "<f4") {
|
} else if (info.descr === "|s8") {
|
||||||
data = new Float32Array(buf, offsetBytes);
|
// 8 byte strings
|
||||||
} else if (info.descr === "<f8") {
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
data = new Float64Array(buf, offsetBytes);
|
} else if (info.descr === "|s15") {
|
||||||
} else if (info.descr === "<u8") {
|
// 15 byte strings?
|
||||||
// 8 byte uint64s
|
data = new Uint8Array(buf, offsetBytes);
|
||||||
data = new Uint8Array(buf, offsetBytes);
|
} else {
|
||||||
} else if (info.descr === "<i8") {
|
throw new Error("unknown numeric dtype " + info.descr);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
open: open,
|
shape: info.shape,
|
||||||
promise: promise,
|
fortran_order: info.fortran_order,
|
||||||
fromArrayBuffer: fromArrayBuffer,
|
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;
|
module.exports = NumpyLoader;
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
export function swapKeysAndValues(obj, f) {
|
export function swapKeysAndValues(obj, f) {
|
||||||
return Object.keys(obj).reduce(function(acc, k) {
|
return Object.keys(obj).reduce(function(acc, k) {
|
||||||
acc[obj[k]] = k;
|
acc[obj[k]] = k;
|
||||||
return acc
|
return acc;
|
||||||
},{})
|
}, {});
|
||||||
};
|
}
|
||||||
|
|
||||||
export function fromArray(arr) {
|
export function fromArray(arr) {
|
||||||
// arr is an array of array key-value pairs
|
// arr is an array of array key-value pairs
|
||||||
// like [['a', 1], ['b', 2]]
|
// like [['a', 1], ['b', 2]]
|
||||||
|
|
||||||
const pairs = arr.map( ([k,v]) => ({[k]: v}));
|
const pairs = arr.map(([k, v]) => ({ [k]: v }));
|
||||||
if(pairs.length > 0) {
|
if (pairs.length > 0) {
|
||||||
return Object.assign(...pairs);
|
return Object.assign(...pairs);
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
|
||||||
export function hash(str) {
|
export function hash(str) {
|
||||||
var hash = 5381,
|
var hash = 5381,
|
||||||
i = str.length;
|
i = str.length;
|
||||||
|
|
||||||
while(i) {
|
while (i) {
|
||||||
hash = (hash * 33) ^ str.charCodeAt(--i);
|
hash = (hash * 33) ^ str.charCodeAt(--i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,173 +1,184 @@
|
||||||
import {createClassFromSpec} from 'react-vega';
|
import { createClassFromSpec } from "react-vega";
|
||||||
|
|
||||||
const canHistogramSpec = {
|
const canHistogramSpec = {
|
||||||
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
|
$schema: "https://vega.github.io/schema/vega/v3.0.json",
|
||||||
"width": 1000,
|
width: 1000,
|
||||||
"height": 100,
|
height: 100,
|
||||||
"padding": 5,
|
padding: 5,
|
||||||
|
|
||||||
"signals": [
|
signals: [
|
||||||
{
|
{
|
||||||
"name": "segment"
|
name: "segment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"data": [
|
data: [
|
||||||
{
|
{
|
||||||
"name": "binned"
|
name: "binned"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"scales": [
|
scales: [
|
||||||
{
|
{
|
||||||
"name": "xscale",
|
name: "xscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"zero": false,
|
zero: false,
|
||||||
"range": "width",
|
range: "width",
|
||||||
"domain": {"data": "binned", "field": "startTime"}
|
domain: { data: "binned", field: "startTime" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xrelscale",
|
name: "xrelscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"zero": false,
|
zero: false,
|
||||||
"range": "width",
|
range: "width",
|
||||||
"domain": {"data": "binned", "field": "relStartTime"}
|
domain: { data: "binned", field: "relStartTime" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yscale",
|
name: "yscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"range": "height", "round": true,
|
range: "height",
|
||||||
"domain": {"data": "binned", "field": "count"},
|
round: true,
|
||||||
"zero": true, "nice": true
|
domain: { data: "binned", field: "count" },
|
||||||
|
zero: true,
|
||||||
|
nice: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"axes": [
|
axes: [
|
||||||
{"orient": "bottom", "scale": "xrelscale", "zindex": 1, "tickCount": 10},
|
{ orient: "bottom", scale: "xrelscale", zindex: 1, tickCount: 10 },
|
||||||
{"orient": "left", "scale": "yscale", "tickCount": 5, "zindex": 1}
|
{ orient: "left", scale: "yscale", tickCount: 5, zindex: 1 }
|
||||||
],
|
],
|
||||||
|
|
||||||
"marks": [
|
marks: [
|
||||||
{"type": "group",
|
{
|
||||||
"name": "histogram",
|
type: "group",
|
||||||
"interactive": true,
|
name: "histogram",
|
||||||
"encode": {
|
interactive: true,
|
||||||
"enter": {
|
encode: {
|
||||||
"height": {"value": 75},
|
enter: {
|
||||||
"width": {"value": 1000},
|
height: { value: 75 },
|
||||||
"fill": {"value": "transparent"}
|
width: { value: 1000 },
|
||||||
}
|
fill: { value: "transparent" }
|
||||||
},
|
}
|
||||||
"signals": [
|
},
|
||||||
|
signals: [
|
||||||
{
|
{
|
||||||
"name": "brush", "value": 0,
|
name: "brush",
|
||||||
"on": [
|
value: 0,
|
||||||
|
on: [
|
||||||
{
|
{
|
||||||
"events": "@bins:mousedown",
|
events: "@bins:mousedown",
|
||||||
"update": "[x(), x()]"
|
update: "[x(), x()]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"events": "[@bins:mousedown, window:mouseup] > window:mousemove!",
|
events: "[@bins:mousedown, window:mouseup] > window:mousemove!",
|
||||||
"update": "[brush[0], clamp(x(), 0, width)]"
|
update: "[brush[0], clamp(x(), 0, width)]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"events": {"signal": "delta"},
|
events: { signal: "delta" },
|
||||||
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
update:
|
||||||
|
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "anchor", "value": null,
|
name: "anchor",
|
||||||
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
|
value: null,
|
||||||
|
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xdown", "value": 0,
|
name: "xdown",
|
||||||
"on": [{"events": "@brush:mousedown", "update": "x()"}]
|
value: 0,
|
||||||
|
on: [{ events: "@brush:mousedown", update: "x()" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "delta", "value": 0,
|
name: "delta",
|
||||||
"on": [
|
value: 0,
|
||||||
|
on: [
|
||||||
{
|
{
|
||||||
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||||
"update": "x() - xdown"
|
update: "x() - xdown"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "segment",
|
name: "segment",
|
||||||
"push": "outer",
|
push: "outer",
|
||||||
"on": [
|
on: [
|
||||||
{
|
{
|
||||||
"events": {"signal": "brush"},
|
events: { signal: "brush" },
|
||||||
"update": "span(brush) ? invert('xscale', brush) : null"
|
update: "span(brush) ? invert('xscale', brush) : null"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"marks": [
|
marks: [
|
||||||
{
|
{
|
||||||
"type": "rect",
|
type: "rect",
|
||||||
"interactive": true,
|
interactive: true,
|
||||||
"from": {"data": "binned"},
|
from: { data: "binned" },
|
||||||
"name": "bins",
|
name: "bins",
|
||||||
"encode": {
|
encode: {
|
||||||
"update": {
|
update: {
|
||||||
"x": {"scale": "xscale", "field": "startTime"},
|
x: { scale: "xscale", field: "startTime" },
|
||||||
"x2": {"scale": "xscale", "field": "endTime",
|
x2: {
|
||||||
"offset": 0},
|
scale: "xscale",
|
||||||
"y": {"scale": "yscale", "field": "count"},
|
field: "endTime",
|
||||||
"y2": {"scale": "yscale", "value": 0},
|
offset: 0
|
||||||
"fill": {"value": "steelblue"}
|
},
|
||||||
|
y: { scale: "yscale", field: "count" },
|
||||||
|
y2: { scale: "yscale", value: 0 },
|
||||||
|
fill: { value: "steelblue" }
|
||||||
},
|
},
|
||||||
"hover": {
|
hover: {
|
||||||
"fill": {"value": "goldenrod"},
|
fill: { value: "goldenrod" },
|
||||||
"cursor": {"value": "ew-resize"}
|
cursor: { value: "ew-resize" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rect",
|
type: "rect",
|
||||||
"interactive": true,
|
interactive: true,
|
||||||
"name": "brush",
|
name: "brush",
|
||||||
"encode": {
|
encode: {
|
||||||
"enter": {
|
enter: {
|
||||||
"y": {"value": 0},
|
y: { value: 0 },
|
||||||
"height": {"value": 100},
|
height: { value: 100 },
|
||||||
"fill": {"value": "#333"},
|
fill: { value: "#333" },
|
||||||
"fillOpacity": {"value": 0.2}
|
fillOpacity: { value: 0.2 }
|
||||||
},
|
},
|
||||||
"update": {
|
update: {
|
||||||
"x": {"signal": "brush[0]"},
|
x: { signal: "brush[0]" },
|
||||||
"x2": {"signal": "brush[1]"}
|
x2: { signal: "brush[1]" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rect",
|
type: "rect",
|
||||||
"interactive": false,
|
interactive: false,
|
||||||
"encode": {
|
encode: {
|
||||||
"enter": {
|
enter: {
|
||||||
"y": {"value": 0},
|
y: { value: 0 },
|
||||||
"height": {"value": 100},
|
height: { value: 100 },
|
||||||
"width": {"value": 2},
|
width: { value: 2 },
|
||||||
"fill": {"value": "firebrick"}
|
fill: { value: "firebrick" }
|
||||||
},
|
},
|
||||||
"update": {
|
update: {
|
||||||
"x": {"signal": "brush[0]"}
|
x: { signal: "brush[0]" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rect",
|
type: "rect",
|
||||||
"interactive": false,
|
interactive: false,
|
||||||
"encode": {
|
encode: {
|
||||||
"enter": {
|
enter: {
|
||||||
"y": {"value": 0},
|
y: { value: 0 },
|
||||||
"height": {"value": 100},
|
height: { value: 100 },
|
||||||
"width": {"value": 2},
|
width: { value: 2 },
|
||||||
"fill": {"value": "firebrick"}
|
fill: { value: "firebrick" }
|
||||||
},
|
},
|
||||||
"update": {
|
update: {
|
||||||
"x": {"signal": "brush[1]"}
|
x: { signal: "brush[1]" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,306 +1,334 @@
|
||||||
import {createClassFromSpec} from 'react-vega';
|
import { createClassFromSpec } from "react-vega";
|
||||||
|
|
||||||
export default createClassFromSpec('CanPlot', {
|
export default createClassFromSpec("CanPlot", {
|
||||||
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
|
$schema: "https://vega.github.io/schema/vega/v3.0.json",
|
||||||
"width": 500,
|
width: 500,
|
||||||
"height": 200,
|
height: 200,
|
||||||
"padding": "auto",
|
padding: "auto",
|
||||||
"autosize": {"type": "fit", "resize": true},
|
autosize: { type: "fit", resize: true },
|
||||||
"signals": [
|
signals: [
|
||||||
{
|
{
|
||||||
"name": "tipTime",
|
name: "tipTime",
|
||||||
"on": [{
|
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": [
|
|
||||||
{
|
{
|
||||||
"type": "filter",
|
events: "mousemove",
|
||||||
"expr": "abs(datum.relTime - tipTime) <= 0.01"
|
update: "invert('xrelscale', x())"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "aggregate",
|
|
||||||
"fields": ["relTime", "y", "unit"],
|
|
||||||
"ops": ["min", "argmin", "argmin"],
|
|
||||||
"as": ["min", "argmin", "argmin"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ySegmentScale",
|
name: "clickTime",
|
||||||
"source": "table",
|
on: [
|
||||||
"transform": [
|
|
||||||
{
|
{
|
||||||
"type": "filter",
|
events: "mouseup",
|
||||||
"expr": "length(segment) != 2 || (datum.relTime >= segment[0] && datum.relTime <= segment[1])"
|
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",
|
name: "xscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"range": "width",
|
range: "width",
|
||||||
"domain": {"data": "table", "field": "x"},
|
domain: { data: "table", field: "x" },
|
||||||
"zero": false
|
zero: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xrelscale",
|
name: "xrelscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"range": "width",
|
range: "width",
|
||||||
"domain": {"data": "table", "field": "relTime"},
|
domain: { data: "table", field: "relTime" },
|
||||||
"zero": false,
|
zero: false,
|
||||||
"clamp": true,
|
clamp: true,
|
||||||
"domainRaw": {"signal": "segment"}
|
domainRaw: { signal: "segment" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yscale",
|
name: "yscale",
|
||||||
"type": "linear",
|
type: "linear",
|
||||||
"range": "height",
|
range: "height",
|
||||||
"clamp": true,
|
clamp: true,
|
||||||
"zero": false,
|
zero: false,
|
||||||
"domain": {"signal": "ySegment"}
|
domain: { signal: "ySegment" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "color",
|
name: "color",
|
||||||
"type": "ordinal",
|
type: "ordinal",
|
||||||
"domain": {"data": "table", "field": "color"},
|
domain: { data: "table", field: "color" },
|
||||||
"range": {"data": "table", "field": "color"}
|
range: { data: "table", field: "color" }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"axes": [
|
axes: [
|
||||||
{"orient": "bottom", "scale": "xrelscale", labelOverlap: true},
|
{ orient: "bottom", scale: "xrelscale", labelOverlap: true },
|
||||||
{"orient": "left", "scale": "yscale"}
|
{ orient: "left", scale: "yscale" }
|
||||||
],
|
],
|
||||||
"marks": [
|
marks: [
|
||||||
{"type": "group",
|
{
|
||||||
"name": "plot",
|
type: "group",
|
||||||
"interactive": true,
|
name: "plot",
|
||||||
"encode": {
|
interactive: true,
|
||||||
"enter": {
|
encode: {
|
||||||
"width": {"signal": "width"},
|
enter: {
|
||||||
"height": {"signal": "height"},
|
width: { signal: "width" },
|
||||||
"fill": {"value": "transparent"}
|
height: { signal: "height" },
|
||||||
}
|
fill: { value: "transparent" }
|
||||||
},
|
}
|
||||||
"signals": [
|
},
|
||||||
|
signals: [
|
||||||
{
|
{
|
||||||
"name": "brush", "value": 0,
|
name: "brush",
|
||||||
"on": [
|
value: 0,
|
||||||
|
on: [
|
||||||
{
|
{
|
||||||
"events": "@boundingRect:mousedown",
|
events: "@boundingRect:mousedown",
|
||||||
"update": "[x(), x()]"
|
update: "[x(), x()]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"events": "[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
|
events:
|
||||||
"update": "[brush[0], clamp(x(), 0, width)]"
|
"[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
|
||||||
|
update: "[brush[0], clamp(x(), 0, width)]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"events": {"signal": "delta"},
|
events: { signal: "delta" },
|
||||||
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
update:
|
||||||
|
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "anchor", "value": null,
|
name: "anchor",
|
||||||
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
|
value: null,
|
||||||
|
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xdown", "value": 0,
|
name: "xdown",
|
||||||
"on": [{"events": "@brush:mousedown", "update": "x()"}]
|
value: 0,
|
||||||
|
on: [{ events: "@brush:mousedown", update: "x()" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "delta", "value": 0,
|
name: "delta",
|
||||||
"on": [
|
value: 0,
|
||||||
|
on: [
|
||||||
{
|
{
|
||||||
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
|
||||||
"update": "x() - xdown"
|
update: "x() - xdown"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "segment",
|
name: "segment",
|
||||||
"push": "outer",
|
push: "outer",
|
||||||
"on": [
|
on: [
|
||||||
{
|
{
|
||||||
"events": "window:mouseup",
|
events: "window:mouseup",
|
||||||
"update": "span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
|
update:
|
||||||
|
"span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"marks": [
|
marks: [
|
||||||
{
|
{
|
||||||
"type": "group",
|
type: "group",
|
||||||
"from": {
|
from: {
|
||||||
"facet": {
|
facet: {
|
||||||
"name": "series",
|
name: "series",
|
||||||
"data": "table",
|
data: "table",
|
||||||
"groupby": "color"
|
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": {
|
update: {
|
||||||
"type": "line",
|
x: { signal: "brush[0]" },
|
||||||
"name": "lineMark",
|
x2: { signal: "brush[1]" }
|
||||||
"from": {"data": "series"},
|
}
|
||||||
"interactive": true,
|
}
|
||||||
"encode": {
|
},
|
||||||
"update": {
|
// {
|
||||||
"interpolate": {"value": "step"},
|
// "type": "rect",
|
||||||
"x": {"scale": "xrelscale", "field": "relTime"},
|
// "interactive": false,
|
||||||
"y": {"scale": "yscale", "field": "y"}
|
// "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": {
|
{ scale: "xrelscale", field: "argmin.relTime", offset: -80 }
|
||||||
"fillOpacity": {"value": 0.5}
|
],
|
||||||
},
|
y: { scale: "yscale", field: "argmin.y" },
|
||||||
"enter": {
|
height: { value: 20 },
|
||||||
"clip": {"value": true},
|
width: { value: 80 },
|
||||||
"stroke": {"scale": "color", "field": "color"},
|
fill: { value: "#fff" },
|
||||||
"strokeWidth": {"value": 2}
|
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: "rect",
|
||||||
"type": "text",
|
name: "boundingRect",
|
||||||
"interactive": false,
|
interactive: true,
|
||||||
"encode": {
|
encode: {
|
||||||
"update": {
|
enter: {
|
||||||
"text": {"signal": "format(parent.argmin.relTime, ',.2f') + ': ' + format(parent.argmin.y, ',.2f') + ' ' + parent.argmin.unit"},
|
width: { signal: "width" },
|
||||||
"fill": {"value": "black"},
|
height: { signal: "height" },
|
||||||
"fontWeight": {"value": "bold"},
|
fill: { value: "transparent" }
|
||||||
"y": {"value": 20}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rect",
|
|
||||||
"name": "boundingRect",
|
|
||||||
"interactive": true,
|
|
||||||
"encode": {
|
|
||||||
"enter": {
|
|
||||||
"width": {"signal": "width"},
|
|
||||||
"height": {"signal": "height"},
|
|
||||||
"fill": {"value": "transparent"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue