prettier the js

main
ChristopherBiscardi 2017-12-12 18:27:20 -08:00
parent 40e7efad4c
commit beb0a7ea01
No known key found for this signature in database
GPG Key ID: 703265E1DE405983
94 changed files with 7250 additions and 6338 deletions

View File

@ -17,10 +17,13 @@
"github-api": "^3.0.0",
"hls": "0.0.1",
"hls.js": "^0.7.9",
"husky": "^0.14.3",
"int64-buffer": "^0.1.9",
"js-cookie": "^2.1.4",
"left-pad": "^1.1.3",
"lint-staged": "^6.0.0",
"moment": "^2.18.1",
"prettier": "^1.9.2",
"prop-types": "^15.5.10",
"raven-js": "^3.16.0",
"react": "^16.2.0",
@ -57,6 +60,13 @@
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"sass":
"scss src/index.scss:src/index.css; sass --watch src/index.scss:src/index.css"
"scss src/index.scss:src/index.css; sass --watch src/index.scss:src/index.css",
"precommit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx}": ["prettier --parser flow --write", "git add"],
"*.json": ["prettier --parser json --write", "git add"],
"*.{graphql,gql}": ["prettier --parser graphql --write", "git add"],
"*.{md,markdown}": ["prettier --parser markdown --write", "git add"]
}
}

View File

@ -1,25 +1,27 @@
global.__JEST__ = 1
import DBC from '../../models/can/dbc';
import Signal from '../../models/can/signal';
global.__JEST__ = 1;
import DBC from "../../models/can/dbc";
import Signal from "../../models/can/signal";
test('setting signals should create a message', () => {
const dbc = new DBC();
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal'})});
test("setting signals should create a message", () => {
const dbc = new DBC();
dbc.setSignals(100, { "My Signal": new Signal({ name: "My Signal" }) });
expect(dbc.messages.has(100)).toBe(true);
expect(dbc.messages.has(100)).toBe(true);
});
test('setting signals should update DBC.boardUnits', () => {
const dbc = new DBC();
dbc.setSignals(100, {'My Signal': new Signal({name: 'My Signal', receiver: ['NEO']})});
test("setting signals should update DBC.boardUnits", () => {
const dbc = new DBC();
dbc.setSignals(100, {
"My Signal": new Signal({ name: "My Signal", receiver: ["NEO"] })
});
expect(dbc.boardUnits.map((bu) => bu.name).indexOf('NEO')).toBe(0);
expect(dbc.boardUnits.map(bu => bu.name).indexOf("NEO")).toBe(0);
});
test('adding a signal should update DBC.boardUnits', () => {
const dbc = new DBC();
dbc.createFrame(100);
dbc.addSignal(100, new Signal({name: 'My Signal', receiver: ['NEO']}));
test("adding a signal should update DBC.boardUnits", () => {
const dbc = new DBC();
dbc.createFrame(100);
dbc.addSignal(100, new Signal({ name: "My Signal", receiver: ["NEO"] }));
expect(dbc.boardUnits.map((bu) => bu.name).indexOf('NEO')).toBe(0);
});
expect(dbc.boardUnits.map(bu => bu.name).indexOf("NEO")).toBe(0);
});

View File

@ -1,7 +1,7 @@
global.__JEST__ = 1
import DBC, {swapOrder} from '../../models/can/dbc';
import Signal from '../../models/can/signal';
import Bitarray from '../../models/bitarray';
global.__JEST__ = 1;
import DBC, { swapOrder } from "../../models/can/dbc";
import Signal from "../../models/can/signal";
import Bitarray from "../../models/bitarray";
const DBC_MESSAGE_DEF = `BO_ 228 STEERING_CONTROL: 5 ADAS
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
@ -63,7 +63,7 @@ BO_ 228 STEERING_CONTROL: 5 ADAS
CM_ SG_ 228 STEER_TORQUE "steer torque is the amount of torque in Nm applied";`;
const DBC_SIGNAL_WITH_MULTI_LINE_COMMENT = `
const DBC_SIGNAL_WITH_MULTI_LINE_COMMENT = `
BO_ 228 STEERING_CONTROL: 5 ADAS
SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
@ -137,136 +137,148 @@ VAL_TABLE_ DI_speedUnits 1 "DI_SPEED_KPH" 0 "DI_SPEED_MPH" ;
`;
const steerTorqueSignal = new Signal({
name: 'STEER_TORQUE',
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
receiver: ['EPS'],
unit: ""});
test('DBC parses steering control message', () => {
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
const {signals} = dbcParsed.messages.get(228);
expect(Object.keys(signals).length).toBe(6);
expect(signals['STEER_TORQUE'].equals(steerTorqueSignal)).toBe(true);
name: "STEER_TORQUE",
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
receiver: ["EPS"],
unit: ""
});
test('DBC parses signal comment', () => {
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
const {signals} = dbcParsed.messages.get(228);
test("DBC parses steering control message", () => {
const dbcParsed = new DBC(DBC_MESSAGE_DEF);
const { signals } = dbcParsed.messages.get(228);
expect(signals.STEER_TORQUE.comment).toEqual("steer torque is the amount of torque in Nm applied");
expect(Object.keys(signals).length).toBe(6);
expect(signals["STEER_TORQUE"].equals(steerTorqueSignal)).toBe(true);
});
test('DBC parses multi-line signal comment', () => {
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
const {signals} = dbcParsed.messages.get(228);
test("DBC parses signal comment", () => {
const dbcParsed = new DBC(DBC_SIGNAL_WITH_COMMENT);
const { signals } = dbcParsed.messages.get(228);
expect(signals.STEER_TORQUE.comment).toEqual("steer torque is the\namount of torque in Nm applied");
expect(signals.STEER_TORQUE.comment).toEqual(
"steer torque is the amount of torque in Nm applied"
);
});
test('DBC parses message comment', () => {
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
const msg = dbcParsed.messages.get(228);
test("DBC parses multi-line signal comment", () => {
const dbcParsed = new DBC(DBC_SIGNAL_WITH_MULTI_LINE_COMMENT);
const { signals } = dbcParsed.messages.get(228);
expect(msg.comment).toEqual("this message contains steer torque information");
expect(signals.STEER_TORQUE.comment).toEqual(
"steer torque is the\namount of torque in Nm applied"
);
});
test('DBC parses multi-line message comment', () => {
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
const msg = dbcParsed.messages.get(228);
test("DBC parses message comment", () => {
const dbcParsed = new DBC(DBC_MESSAGE_WITH_COMMENT);
const msg = dbcParsed.messages.get(228);
expect(msg.comment).toEqual("this message contains\nsteer torque information");
expect(msg.comment).toEqual("this message contains steer torque information");
});
test('DBC parses board unit names', () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS);
expect(dbcParsed.boardUnits[0].name).toEqual("first_board_unit");
expect(dbcParsed.boardUnits[1].name).toEqual("second_board_unit");
test("DBC parses multi-line message comment", () => {
const dbcParsed = new DBC(DBC_MESSAGE_WITH_MULTI_LINE_COMMENT);
const msg = dbcParsed.messages.get(228);
expect(msg.comment).toEqual(
"this message contains\nsteer torque information"
);
});
test('DBC parses board unit comments', () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
test("DBC parses board unit names", () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS);
expect(dbcParsed.boardUnits[0].name).toEqual("first_board_unit");
expect(dbcParsed.boardUnits[1].name).toEqual("second_board_unit");
});
test('DBC parses multi-line board unit comments', () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
test("DBC parses board unit comments", () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT);
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit comment");
});
test('DBC parses signal value descriptions', () => {
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
const {signals} = dbcParsed.messages.get(228);
const expectedTorqueRequestVals = new Map([['1', 'requesting torque'],
['0', 'not requesting torque']]);
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(expectedTorqueRequestVals);
test("DBC parses multi-line board unit comments", () => {
const dbcParsed = new DBC(DBC_BOARD_UNITS_WITH_COMMENT_LINES);
expect(dbcParsed.boardUnits[0].comment).toEqual("first board unit\ncomment");
});
test('DBC parses value tables', () => {
const dbcParsed = new DBC(DBC_VALUE_TABLE);
const stateTableEntries = [['4', "DI_STATE_ENABLE"],
['3', "DI_STATE_FAULT"],
['2', "DI_STATE_CLEAR_FAULT"],
['1', "DI_STATE_STANDBY"],
['0', "DI_STATE_PREAUTH"]]
const stateTable = new Map(stateTableEntries);
const speedUnitsEntries = [['1',"DI_SPEED_KPH"],
['0',"DI_SPEED_MPH"]]
const speedUnitsTable = new Map(speedUnitsEntries);
test("DBC parses signal value descriptions", () => {
const dbcParsed = new DBC(DBC_SIGNALS_WITH_VAL);
const { signals } = dbcParsed.messages.get(228);
const valueTableEntries = Array.from(dbcParsed.valueTables.entries());
expect(valueTableEntries[0]).toEqual(['DI_state', stateTable]);
expect(valueTableEntries[1]).toEqual(['DI_speedUnits', speedUnitsTable]);
const expectedTorqueRequestVals = new Map([
["1", "requesting torque"],
["0", "not requesting torque"]
]);
expect(signals.STEER_TORQUE_REQUEST.valueDescriptions).toEqual(
expectedTorqueRequestVals
);
});
test('swapOrder properly converts little endian to big endian', () => {
const littleEndianHex = 'e2d62a0bd0d3b5e5';
const bigEndianHex = 'e5b5d3d00b2ad6e2';
test("DBC parses value tables", () => {
const dbcParsed = new DBC(DBC_VALUE_TABLE);
const stateTableEntries = [
["4", "DI_STATE_ENABLE"],
["3", "DI_STATE_FAULT"],
["2", "DI_STATE_CLEAR_FAULT"],
["1", "DI_STATE_STANDBY"],
["0", "DI_STATE_PREAUTH"]
];
const stateTable = new Map(stateTableEntries);
const speedUnitsEntries = [["1", "DI_SPEED_KPH"], ["0", "DI_SPEED_MPH"]];
const speedUnitsTable = new Map(speedUnitsEntries);
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
const valueTableEntries = Array.from(dbcParsed.valueTables.entries());
expect(valueTableEntries[0]).toEqual(["DI_state", stateTable]);
expect(valueTableEntries[1]).toEqual(["DI_speedUnits", speedUnitsTable]);
});
test('int32 parser produces correct value for steer torque signal', () => {
const dbc = new DBC(DBC_MESSAGE_DEF);
test("swapOrder properly converts little endian to big endian", () => {
const littleEndianHex = "e2d62a0bd0d3b5e5";
const bigEndianHex = "e5b5d3d00b2ad6e2";
const hex = 'e2d62a0bd0d3b5e5';
const buffer = Buffer.from(hex, 'hex');
const bufferSwapped = Buffer.from(buffer).swap64();
const littleEndianHexSwapped = swapOrder(littleEndianHex, 16, 2);
const bitArr = Bitarray.fromBytes(buffer);
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
const value = dbc.valueForInt32Signal(steerTorqueSignal, bitArr, bitsSwapped);
expect(value).toBe(-7466);
expect(littleEndianHexSwapped == bigEndianHex).toBe(true);
});
test('int64 parser produces correct value for steer torque signal', () => {
const dbc = new DBC(DBC_MESSAGE_DEF);
test("int32 parser produces correct value for steer torque signal", () => {
const dbc = new DBC(DBC_MESSAGE_DEF);
const hex = 'e2d62a0bd0d3b5e5';
const value = dbc.valueForInt64Signal(steerTorqueSignal, hex);
const hex = "e2d62a0bd0d3b5e5";
const buffer = Buffer.from(hex, "hex");
const bufferSwapped = Buffer.from(buffer).swap64();
expect(value).toBe(-7466);
const bitArr = Bitarray.fromBytes(buffer);
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
const value = dbc.valueForInt32Signal(steerTorqueSignal, bitArr, bitsSwapped);
expect(value).toBe(-7466);
});
test("int64 parser produces correct value for steer torque signal", () => {
const dbc = new DBC(DBC_MESSAGE_DEF);
const hex = "e2d62a0bd0d3b5e5";
const value = dbc.valueForInt64Signal(steerTorqueSignal, hex);
expect(value).toBe(-7466);
});
function dbcInt32SignalValue(dbc, signalSpec, hex) {
const buffer = Buffer.from(hex, 'hex');
const bufferSwapped = Buffer.from(buffer).swap64();
const buffer = Buffer.from(hex, "hex");
const bufferSwapped = Buffer.from(buffer).swap64();
const bits = Bitarray.fromBytes(buffer);
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
const bits = Bitarray.fromBytes(buffer);
const bitsSwapped = Bitarray.fromBytes(bufferSwapped);
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
return dbc.valueForInt32Signal(signalSpec, bits, bitsSwapped);
}
const DBC_BINARY_LE_SIGNAL = `
@ -274,56 +286,53 @@ BO_ 768 NEW_MSG_1: 8 XXX
SG_ NEW_SIGNAL_1 : 37|1@1+ (1,0) [0|1] "" XXX
`;
test('int32 parsers produces correct value for binary little endian signal', () => {
const dbc = new DBC(DBC_BINARY_LE_SIGNAL)
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
test("int32 parsers produces correct value for binary little endian signal", () => {
const dbc = new DBC(DBC_BINARY_LE_SIGNAL);
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
const hexDataSet = '0000000020000000';
const hexDataNotSet = '0000000000000000';
const hexDataSet = "0000000020000000";
const hexDataNotSet = "0000000000000000";
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
const setValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSet);
const notSetValue = dbcInt32SignalValue(dbc, signalSpec, hexDataNotSet);
expect(setValue).toEqual(1);
expect(notSetValue).toEqual(0);
expect(setValue).toEqual(1);
expect(notSetValue).toEqual(0);
});
const DBC_TWO_BIT_LE_SIGNAL = `
BO_ 768 NEW_MSG_1: 8 XXX
SG_ NEW_SIGNAL_1 : 35|2@1+ (1,0) [0|3] "" XXX
`;
test('int32 parser produces correct value for 2-bit little endian signal spanning words', () => {
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
test("int32 parser produces correct value for 2-bit little endian signal spanning words", () => {
const dbc = new DBC(DBC_TWO_BIT_LE_SIGNAL);
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
const hexData = '00000001f8000000';
const hexData = "00000001f8000000";
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
expect(value).toEqual(3);
const value = dbcInt32SignalValue(dbc, signalSpec, hexData);
expect(value).toEqual(3);
});
const DBC_FOUR_BIT_LE_SIGNAL = `
BO_ 768 NEW_MSG_1: 8 XXX
SG_ NEW_SIGNAL_1 : 6|4@1+ (1,0) [0|15] "" XXX
`;
test('int32 parser produces correct value for 4-bit little endian signal', () => {
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
const signalSpec = dbc.messages.get(768).signals['NEW_SIGNAL_1'];
test("int32 parser produces correct value for 4-bit little endian signal", () => {
const dbc = new DBC(DBC_FOUR_BIT_LE_SIGNAL);
const signalSpec = dbc.messages.get(768).signals["NEW_SIGNAL_1"];
// this data is symmetric, the data bits are 1111
const hexDataSymmetric = 'f00f000000000000';
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
expect(symValue).toEqual(15);
// this data is symmetric, the data bits are 1111
const hexDataSymmetric = "f00f000000000000";
const symValue = dbcInt32SignalValue(dbc, signalSpec, hexDataSymmetric);
expect(symValue).toEqual(15);
// this data is asymmetric, the data bits are 1101
const hexDataAsymmetric = 'f002000000000000';
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
expect(aSymValue).toEqual(11);
// this data is asymmetric, the data bits are 1101
const hexDataAsymmetric = "f002000000000000";
const aSymValue = dbcInt32SignalValue(dbc, signalSpec, hexDataAsymmetric);
expect(aSymValue).toEqual(11);
});
const DBC_CHFFR_METRIC_COMMENT = `
BO_ 37 STEERING_CONTROL: 8 XXX
SG_ STEER_ANGLE : 6|4@1+ (1,0) [0|15] "" XXX
@ -331,10 +340,12 @@ BO_ 37 STEERING_CONTROL: 8 XXX
CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180";
`;
test('dbc parser parses top-level comment with chffr metric', () => {
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
const { comments } = dbc;
test("dbc parser parses top-level comment with chffr metric", () => {
const dbc = new DBC(DBC_CHFFR_METRIC_COMMENT);
const { comments } = dbc;
expect(comments.length).toEqual(1);
expect(comments[0]).toEqual("CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180");
})
expect(comments.length).toEqual(1);
expect(comments[0]).toEqual(
"CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"
);
});

View File

@ -1,17 +1,17 @@
global.__JEST__ = 1
import DBC, {swapOrder} from '../../models/can/dbc';
import { ACURA_DBC } from '../res/acura-dbc';
import { CRV_DBC } from '../res/crv-dbc';
import { TESLA_DBC } from '../res/tesla-dbc';
global.__JEST__ = 1;
import DBC, { swapOrder } from "../../models/can/dbc";
import { ACURA_DBC } from "../res/acura-dbc";
import { CRV_DBC } from "../res/crv-dbc";
import { TESLA_DBC } from "../res/tesla-dbc";
test('DBC.text() for acura DBC should be equivalent to its original text', () => {
const dbc = new DBC(ACURA_DBC);
test("DBC.text() for acura DBC should be equivalent to its original text", () => {
const dbc = new DBC(ACURA_DBC);
expect(dbc.text()).toBe(ACURA_DBC);
expect(dbc.text()).toBe(ACURA_DBC);
});
test('DBC.text() for crv DBC should be equivalent to its original text', () => {
const dbc = new DBC(CRV_DBC);
test("DBC.text() for crv DBC should be equivalent to its original text", () => {
const dbc = new DBC(CRV_DBC);
expect(dbc.text()).toBe(CRV_DBC);
expect(dbc.text()).toBe(CRV_DBC);
});

View File

@ -1,9 +1,17 @@
import Entries from '../../models/can/entries';
import Entries from "../../models/can/entries";
test('segment index low is inclusive and index high is exclusive', () => {
const entries = [{time: 1.0}, {time: 3.45}, {time: 3.65}, {time: 5.55}];
const [segmentIdxLow, segmentIdxHi] = Entries.findSegmentIndices(entries, [3.45, 5.55]);
test("segment index low is inclusive and index high is exclusive", () => {
const entries = [
{ time: 1.0 },
{ time: 3.45 },
{ time: 3.65 },
{ time: 5.55 }
];
const [segmentIdxLow, segmentIdxHi] = Entries.findSegmentIndices(entries, [
3.45,
5.55
]);
expect(segmentIdxLow).toBe(1);
expect(segmentIdxHi).toBe(entries.length - 1);
})
expect(segmentIdxLow).toBe(1);
expect(segmentIdxHi).toBe(entries.length - 1);
});

View File

@ -1,8 +1,13 @@
import Frame from '../../models/can/frame';
import Frame from "../../models/can/frame";
const FRAME_HEADER = "BO_ 255 SOME_FRAME: 5 ADAS";
test('Frame.header() returns spec compliant representation', () => {
const frame = new Frame({name: 'SOME_FRAME', id: 255, size: 5, transmitters: ['ADAS']});
expect(frame.header()).toEqual(FRAME_HEADER);
test("Frame.header() returns spec compliant representation", () => {
const frame = new Frame({
name: "SOME_FRAME",
id: 255,
size: 5,
transmitters: ["ADAS"]
});
expect(frame.header()).toEqual(FRAME_HEADER);
});

View File

@ -1,77 +1,104 @@
import Signal from '../../models/can/signal';
import Signal from "../../models/can/signal";
const someSignalParams = {
name: 'STEER_TORQUE',
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""};
name: "STEER_TORQUE",
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""
};
const someOtherSignalParams = {
name: 'DIFFERENT_NAME',
startBit: 0,
name: "DIFFERENT_NAME",
startBit: 0,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""
};
test("Signal.equals returns true for signals with identical properties", () => {
const someSignal = new Signal(someSignalParams);
const someEquivalentSignal = new Signal(someSignalParams);
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
});
test("Signal.equals returns false for signals with different properties", () => {
const someSignal = new Signal(someSignalParams);
const differentSignal = new Signal(someOtherSignalParams);
expect(someSignal.equals(differentSignal)).toBe(false);
});
test("Signal.bitDescription returns proper description for a little endian signal", () => {
const littleEndianSignal = new Signal({
name: "little endian signal",
startBit: 20,
size: 4,
isLittleEndian: true
});
expect(littleEndianSignal.bitDescription(20).bitNumber).toBe(0);
expect(littleEndianSignal.bitDescription(21).bitNumber).toBe(1);
expect(littleEndianSignal.bitDescription(22).bitNumber).toBe(2);
expect(littleEndianSignal.bitDescription(23).bitNumber).toBe(3);
});
test("Signal.bitDescription returns proper description for a big endian signal", () => {
const bigEndianSignal = new Signal({
name: "big endian signal",
startBit: 7,
size: 16,
isLittleEndian: false,
isSigned: true,
factor: 1,
offset: 0,
min: -3840,
max: 3840,
unit: ""};
isLittleEndian: false
});
test('Signal.equals returns true for signals with identical properties', () => {
const someSignal = new Signal(someSignalParams);
const someEquivalentSignal = new Signal(someSignalParams);
expect(someSignal.equals(someEquivalentSignal)).toBe(true);
const bitFifteenDescription = {
bitNumber: 8,
isLsb: false,
isMsb: false,
range: [0, 15]
};
const bitZeroDescription = {
bitNumber: 7,
isLsb: false,
isMsb: false,
range: [0, 15]
};
const bitEightDescription = {
bitNumber: 15,
isLsb: true,
isMsb: false,
range: [0, 15]
};
const bitSevenDescription = {
bitNumber: 0,
isLsb: false,
isMsb: true,
range: [0, 15]
};
expect(bigEndianSignal.bitDescription(15)).toEqual(bitFifteenDescription);
expect(bigEndianSignal.bitDescription(0)).toEqual(bitZeroDescription);
expect(bigEndianSignal.bitDescription(8)).toEqual(bitEightDescription);
expect(bigEndianSignal.bitDescription(7)).toEqual(bitSevenDescription);
});
test('Signal.equals returns false for signals with different properties', () => {
const someSignal = new Signal(someSignalParams);
const differentSignal = new Signal(someOtherSignalParams);
expect(someSignal.equals(differentSignal)).toBe(false);
});
test('Signal.bitDescription returns proper description for a little endian signal', () => {
const littleEndianSignal = new Signal({name: 'little endian signal',
startBit: 20,
size: 4,
isLittleEndian: true});
expect(littleEndianSignal.bitDescription(20).bitNumber).toBe(0);
expect(littleEndianSignal.bitDescription(21).bitNumber).toBe(1);
expect(littleEndianSignal.bitDescription(22).bitNumber).toBe(2);
expect(littleEndianSignal.bitDescription(23).bitNumber).toBe(3);
});
test('Signal.bitDescription returns proper description for a big endian signal', () => {
const bigEndianSignal = new Signal({name: 'big endian signal',
startBit: 7,
size: 16,
isLittleEndian: false});
const bitFifteenDescription = {bitNumber: 8, isLsb: false, isMsb: false, range: [0,15]};
const bitZeroDescription = {bitNumber: 7, isLsb: false, isMsb: false, range: [0,15]};
const bitEightDescription = {bitNumber: 15, isLsb: true, isMsb: false, range: [0,15]};
const bitSevenDescription = {bitNumber: 0, isLsb: false, isMsb: true, range: [0,15]};
expect(bigEndianSignal.bitDescription(15)).toEqual(bitFifteenDescription);
expect(bigEndianSignal.bitDescription(0)).toEqual(bitZeroDescription);
expect(bigEndianSignal.bitDescription(8)).toEqual(bitEightDescription);
expect(bigEndianSignal.bitDescription(7)).toEqual(bitSevenDescription);
});
test('Signal.bitDescription returns null for bit index that is not in its range', () => {
const someSignal = new Signal({name: 'some signal',
startBit: 20,
size: 4,
isLittleEndian: false});
expect(someSignal.bitDescription(21)).toBe(null);
expect(someSignal.bitDescription(16)).toBe(null);
test("Signal.bitDescription returns null for bit index that is not in its range", () => {
const someSignal = new Signal({
name: "some signal",
startBit: 20,
size: 4,
isLittleEndian: false
});
expect(someSignal.bitDescription(21)).toBe(null);
expect(someSignal.bitDescription(16)).toBe(null);
});

View File

@ -5,10 +5,10 @@ note: 'right' and 'left' in test descriptions
refer to the sides of the bit matrix
as displayed to the user.
*/
import AddSignals from '../../components/AddSignals';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
import AddSignals from "../../components/AddSignals";
import React from "react";
import { shallow, mount, render } from "enzyme";
import { StyleSheetTestUtils } from "aphrodite";
// Prevents style injection from firing after test finishes
// and jsdom is torn down.
@ -22,286 +22,332 @@ afterEach(() => {
// signal creation
function createAddSignals(signals) {
if(signals === undefined) {
signals = {};
}
const message = {signals,
address: 0,
entries: [
{relTime: 0,
hexData: '0000000000000000'}
]};
if (signals === undefined) {
signals = {};
}
const message = {
signals,
address: 0,
entries: [
{
relTime: 0,
hexData: "0000000000000000"
}
]
};
const component = shallow(<AddSignals
message={message}
messageIndex={0}
onConfirmedSignalChange={() => {}} />);
const component = shallow(
<AddSignals
message={message}
messageIndex={0}
onConfirmedSignalChange={() => {}}
/>
);
return component;
return component;
}
test('double clicking adds a signal', () => {
const component = createAddSignals();
test("double clicking adds a signal", () => {
const component = createAddSignals();
const firstBit = component.find('.bit').first();
const firstBit = component.find(".bit").first();
firstBit.simulate('dblclick');
const newSignal = Object.values(component.state('signals'))[0];
firstBit.simulate("dblclick");
const newSignal = Object.values(component.state("signals"))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(1);
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(1);
});
test('dragging right to left across a byte creates a little endian signal', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(7);
rightBitInByte.simulate('mousedown');
leftBitInByte.simulate('mouseup');
test("dragging right to left across a byte creates a little endian signal", () => {
const component = createAddSignals();
const leftBitInByte = component.find(".bit").first();
const rightBitInByte = component.find(".bit").at(7);
rightBitInByte.simulate("mousedown");
leftBitInByte.simulate("mouseup");
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
const newSignal = Object.values(component.state("signals"))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
});
test('dragging left to right across a byte creates a big endian signal', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(7);
leftBitInByte.simulate('mousedown');
rightBitInByte.simulate('mouseup');
test("dragging left to right across a byte creates a big endian signal", () => {
const component = createAddSignals();
const leftBitInByte = component.find(".bit").first();
const rightBitInByte = component.find(".bit").at(7);
leftBitInByte.simulate("mousedown");
rightBitInByte.simulate("mouseup");
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
const newSignal = Object.values(component.state("signals"))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(8);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
});
test('dragging from the left of byte 0 to right of byte 1 creates a big endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByte = component.find('.bit').first();
const rightBitInByte = component.find('.bit').at(15);
leftBitInByte.simulate('mousedown');
rightBitInByte.simulate('mouseup');
test("dragging from the left of byte 0 to right of byte 1 creates a big endian signal spanning both bytes", () => {
const component = createAddSignals();
const leftBitInByte = component.find(".bit").first();
const rightBitInByte = component.find(".bit").at(15);
leftBitInByte.simulate("mousedown");
rightBitInByte.simulate("mouseup");
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
const newSignal = Object.values(component.state("signals"))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(false);
expect(newSignal.startBit).toBe(7);
});
test('dragging from the right of byte 0 to the left of byte 1 creates a little endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteOne = component.find('.bit').at(8); // left of byte 1
const rightBitInByteZero = component.find('.bit').at(7); // right of byte 0
test("dragging from the right of byte 0 to the left of byte 1 creates a little endian signal spanning both bytes", () => {
const component = createAddSignals();
const leftBitInByteOne = component.find(".bit").at(8); // left of byte 1
const rightBitInByteZero = component.find(".bit").at(7); // right of byte 0
rightBitInByteZero.simulate('mousedown');
leftBitInByteOne.simulate('mouseup');
rightBitInByteZero.simulate("mousedown");
leftBitInByteOne.simulate("mouseup");
const newSignal = Object.values(component.state('signals'))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
const newSignal = Object.values(component.state("signals"))[0];
expect(newSignal).toBeDefined();
expect(newSignal.size).toBe(16);
expect(newSignal.isLittleEndian).toBe(true);
expect(newSignal.startBit).toBe(0);
});
test('dragging from the left of byte 1 to the right of byte 0 creates a little endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteOne = component.find('.bit').at(8);
const rightBitInByteZero = component.find('.bit').at(7);
test("dragging from the left of byte 1 to the right of byte 0 creates a little endian signal spanning both bytes", () => {
const component = createAddSignals();
const leftBitInByteOne = component.find(".bit").at(8);
const rightBitInByteZero = component.find(".bit").at(7);
leftBitInByteOne.simulate('mousedown');
rightBitInByteZero.simulate('mouseup');
leftBitInByteOne.simulate("mousedown");
rightBitInByteZero.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging from the right of byte 1 to the left of byte 0 creates a big endian signal spanning both bytes', () => {
const component = createAddSignals();
const leftBitInByteZero = component.find('.bit').at(0);
const rightBitInByteOne = component.find('.bit').at(15);
test("dragging from the right of byte 1 to the left of byte 0 creates a big endian signal spanning both bytes", () => {
const component = createAddSignals();
const leftBitInByteZero = component.find(".bit").at(0);
const rightBitInByteOne = component.find(".bit").at(15);
rightBitInByteOne.simulate('mousedown');
leftBitInByteZero.simulate('mouseup');
rightBitInByteOne.simulate("mousedown");
leftBitInByteZero.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(16);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
// signal mutation
test('dragging a one-bit big-endian signal to the right should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: false});
test("dragging a one-bit big-endian signal to the right should extend it to the right of the byte", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 7, size: 1, isLittleEndian: false });
const signalBit = component.find('.bit').at(0);
signalBit.simulate('mousedown');
for(let i = 1; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signalBit = component.find(".bit").at(0);
signalBit.simulate("mousedown");
for (let i = 1; i < 8; i++) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
const bitAtRightOfFirstByte = component.find(".bit").at(7);
bitAtRightOfFirstByte.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('dragging a one-bit little-endian signal to the right should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 1, isLittleEndian: true});
test("dragging a one-bit little-endian signal to the right should extend it to the right of the byte", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 7, size: 1, isLittleEndian: true });
const signalBit = component.find('.bit').at(0);
signalBit.simulate('mousedown');
for(let i = 1; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const signalBit = component.find(".bit").at(0);
signalBit.simulate("mousedown");
for (let i = 1; i < 8; i++) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
const bitAtRightOfFirstByte = component.find(".bit").at(7);
bitAtRightOfFirstByte.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging a one-bit big-endian signal to the left should extend it to the left of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 0, size: 1, isLittleEndian: false});
test("dragging a one-bit big-endian signal to the left should extend it to the left of the byte", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 0, size: 1, isLittleEndian: false });
const signalBit = component.find('.bit').at(7);
signalBit.simulate('mousedown');
for(let i = 6; i > -1; i--) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(0);
bitAtRightOfFirstByte.simulate('mouseup');
const signalBit = component.find(".bit").at(7);
signalBit.simulate("mousedown");
for (let i = 6; i > -1; i--) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
const bitAtRightOfFirstByte = component.find(".bit").at(0);
bitAtRightOfFirstByte.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('extending a two-bit big-endian signal by its LSB should extend it to the right of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 2, isLittleEndian: false});
test("extending a two-bit big-endian signal by its LSB should extend it to the right of the byte", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 7, size: 2, isLittleEndian: false });
const lsb = component.find('.bit').at(1);
lsb.simulate('mousedown');
for(let i = 0; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const lsb = component.find(".bit").at(1);
lsb.simulate("mousedown");
for (let i = 0; i < 8; i++) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
const bitAtRightOfFirstByte = component.find(".bit").at(7);
bitAtRightOfFirstByte.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('a two-bit little-endian signal should extend by its LSB to the end of the byte', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 6, size: 2, isLittleEndian: true});
test("a two-bit little-endian signal should extend by its LSB to the end of the byte", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 6, size: 2, isLittleEndian: true });
const lsb = component.find('.bit').at(1);
lsb.simulate('mousedown');
for(let i = 0; i < 8; i++) {
component.find('.bit').at(i).simulate('mouseenter');
}
const bitAtRightOfFirstByte = component.find('.bit').at(7);
bitAtRightOfFirstByte.simulate('mouseup');
const lsb = component.find(".bit").at(1);
lsb.simulate("mousedown");
for (let i = 0; i < 8; i++) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
const bitAtRightOfFirstByte = component.find(".bit").at(7);
bitAtRightOfFirstByte.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
test("dragging the lsb of a little-endian signal spanning an entire byte should not be allowed to pass the MSB", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
const lsb = component.find(".bit").at(7);
lsb.simulate("mousedown");
const bitPastMsb = component.find('.bit').at(15);
bitPastMsb.simulate('mouseenter');
bitPastMsb.simulate('mouseup');
const bitPastMsb = component.find(".bit").at(15);
bitPastMsb.simulate("mouseenter");
bitPastMsb.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 7, size: 8, isLittleEndian: false});
test("dragging the lsb of a big-endian signal towards the msb in the same byte should contract the signal", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 7, size: 8, isLittleEndian: false });
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
for(let i = 6; i > 0; i--) {
component.find('.bit').at(i).simulate('mouseenter');
}
component.find('.bit').at(1).simulate('mouseup');
const lsb = component.find(".bit").at(7);
lsb.simulate("mousedown");
for (let i = 6; i > 0; i--) {
component
.find(".bit")
.at(i)
.simulate("mouseenter");
}
component
.find(".bit")
.at(1)
.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(2);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(2);
expect(signal.isLittleEndian).toBe(false);
expect(signal.startBit).toBe(7);
});
test('a big endian signal spanning one byte should switch to little endian preserving its bit coverage', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 0, size: 8, isLittleEndian: true});
test("a big endian signal spanning one byte should switch to little endian preserving its bit coverage", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 0, size: 8, isLittleEndian: true });
const lsb = component.find('.bit').at(7);
lsb.simulate('mousedown');
const lsb = component.find(".bit").at(7);
lsb.simulate("mousedown");
const bitPastMsb = component.find('.bit').at(15);
bitPastMsb.simulate('mouseenter');
bitPastMsb.simulate('mouseup');
const bitPastMsb = component.find(".bit").at(15);
bitPastMsb.simulate("mouseenter");
bitPastMsb.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(8);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(0);
});
test('dragging the msb of a 2-bit little endian signal to a lower byte should not change the signal', () => {
const component = createAddSignals();
component.instance().createSignal({startBit: 14, size: 2, isLittleEndian: true});
test("dragging the msb of a 2-bit little endian signal to a lower byte should not change the signal", () => {
const component = createAddSignals();
component
.instance()
.createSignal({ startBit: 14, size: 2, isLittleEndian: true });
const msb = component.find('.bit').at(8);
msb.simulate('mousedown');
const bitOutOfBounds = component.find('.bit').at(0);
bitOutOfBounds.simulate('mouseenter');
bitOutOfBounds.simulate('mouseup');
const msb = component.find(".bit").at(8);
msb.simulate("mousedown");
const bitOutOfBounds = component.find(".bit").at(0);
bitOutOfBounds.simulate("mouseenter");
bitOutOfBounds.simulate("mouseup");
const signal = Object.values(component.state('signals'))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(2);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(14);
const signal = Object.values(component.state("signals"))[0];
expect(signal).toBeDefined();
expect(signal.size).toBe(2);
expect(signal.isLittleEndian).toBe(true);
expect(signal.startBit).toBe(14);
});

View File

@ -1,9 +1,9 @@
global.__JEST__ = 1
import CanExplorer from '../../CanExplorer';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
global.__JEST__ = 1;
import CanExplorer from "../../CanExplorer";
import React from "react";
import { shallow, mount, render } from "enzyme";
import { StyleSheetTestUtils } from "aphrodite";
test('CanExplorer renders', () => {
const canExplorer = shallow(<CanExplorer />);
});
test("CanExplorer renders", () => {
const canExplorer = shallow(<CanExplorer />);
});

View File

@ -1,29 +1,31 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import CanGraph from '../../components/CanGraph';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import CanGraph from "../../components/CanGraph";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('CanGraph successfully mounts with minimal default props', () => {
const component = shallow(<CanGraph
onGraphRefAvailable={() => {}}
unplot={() => {}}
messages={{}}
messageId={null}
messageName={null}
signalSpec={null}
onSegmentChanged={() => {}}
segment={[]}
data={[]}
onRelativeTimeClick={() => {}}
currentTime={0}
onDragStart={() => {}}
onDragEnd={() => {}}
container={null}
dragPos={null}
canReceiveGraphDrop={false}
plottedSignals={[]}
live={true}
/>);
expect(component.exists()).toBe(true);
test("CanGraph successfully mounts with minimal default props", () => {
const component = shallow(
<CanGraph
onGraphRefAvailable={() => {}}
unplot={() => {}}
messages={{}}
messageId={null}
messageName={null}
signalSpec={null}
onSegmentChanged={() => {}}
segment={[]}
data={[]}
onRelativeTimeClick={() => {}}
currentTime={0}
onDragStart={() => {}}
onDragEnd={() => {}}
container={null}
dragPos={null}
canReceiveGraphDrop={false}
plottedSignals={[]}
live={true}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,20 +1,23 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import CanGraphList from '../../components/CanGraphList';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import CanGraphList from "../../components/CanGraphList";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('CanGraphList successfully mounts with minimal default props', () => {
const component = shallow(<CanGraphList
plottedSignals={[]}
messages={{}}
graphData={[]}
onGraphTimeClick={() => {}}
seekTime={0}
onSegmentChanged={() => {}}
onSignalUnplotPressed={() => {}}
segment={[]}
mergePlots={() => {}}
live={true} />);
expect(component.exists()).toBe(true);
test("CanGraphList successfully mounts with minimal default props", () => {
const component = shallow(
<CanGraphList
plottedSignals={[]}
messages={{}}
graphData={[]}
onGraphTimeClick={() => {}}
seekTime={0}
onSegmentChanged={() => {}}
onSignalUnplotPressed={() => {}}
segment={[]}
mergePlots={() => {}}
live={true}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,18 +1,21 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import CanLog from '../../components/CanLog';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import CanLog from "../../components/CanLog";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('CanLog successfully mounts with minimal default props', () => {
const component = shallow(<CanLog
message={null}
messageIndex={0}
segmentIndices={[]}
plottedSignals={[]}
onSignalPlotPressed={() => {}}
onSignalUnplotPressed={() => {}}
showAddSignal={() => {}}
onMessageExpanded={() => {}} />);
expect(component.exists()).toBe(true);
test("CanLog successfully mounts with minimal default props", () => {
const component = shallow(
<CanLog
message={null}
messageIndex={0}
segmentIndices={[]}
plottedSignals={[]}
onSignalPlotPressed={() => {}}
onSignalUnplotPressed={() => {}}
showAddSignal={() => {}}
onMessageExpanded={() => {}}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,10 +1,10 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import DbcUpload from '../../components/DbcUpload';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import DbcUpload from "../../components/DbcUpload";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('DbcUpload successfully mounts with minimal default props', () => {
const component = shallow(<DbcUpload />);
expect(component.exists()).toBe(true);
test("DbcUpload successfully mounts with minimal default props", () => {
const component = shallow(<DbcUpload />);
expect(component.exists()).toBe(true);
});

View File

@ -1,20 +1,22 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import EditMessageModal from '../../components/EditMessageModal';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import DbcUtils from '../../utils/dbc';
import DBC from '../../models/can/dbc';
import EditMessageModal from "../../components/EditMessageModal";
import React from "react";
import { shallow, mount, render } from "enzyme";
import DbcUtils from "../../utils/dbc";
import DBC from "../../models/can/dbc";
test('EditMessageModal successfully mounts with minimal default props', () => {
const dbc = new DBC();
const frame = dbc.createFrame(0);
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 1);
test("EditMessageModal successfully mounts with minimal default props", () => {
const dbc = new DBC();
const frame = dbc.createFrame(0);
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 1);
const component = shallow(<EditMessageModal
handleClose={() => {}}
handleSave={() => {}}
message={message}
/>);
expect(component.exists()).toBe(true);
const component = shallow(
<EditMessageModal
handleClose={() => {}}
handleSave={() => {}}
message={message}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,30 +1,32 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import Explorer from '../../components/Explorer';
import React from 'react';
import Moment from 'moment';
import { shallow, mount, render } from 'enzyme';
import Explorer from "../../components/Explorer";
import React from "react";
import Moment from "moment";
import { shallow, mount, render } from "enzyme";
test('Explorer successfully mounts with minimal default props', () => {
const component = shallow(<Explorer
url={null}
live={true}
messages={{}}
selectedMessage={null}
onConfirmedSignalChange={() => {}}
onSeek={() => {}}
onUserSeek={() => {}}
canFrameOffset={0}
firstCanTime={0}
seekTime={0}
seekIndex={0}
currentParts={[0,0]}
partsLoaded={0}
autoplay={true}
showEditMessageModal={() => {}}
onPartChange={() => {}}
routeStartTime={Moment()}
partsCount={0}
/>);
expect(component.exists()).toBe(true);
test("Explorer successfully mounts with minimal default props", () => {
const component = shallow(
<Explorer
url={null}
live={true}
messages={{}}
selectedMessage={null}
onConfirmedSignalChange={() => {}}
onSeek={() => {}}
onUserSeek={() => {}}
canFrameOffset={0}
firstCanTime={0}
seekTime={0}
seekIndex={0}
currentParts={[0, 0]}
partsLoaded={0}
autoplay={true}
showEditMessageModal={() => {}}
onPartChange={() => {}}
routeStartTime={Moment()}
partsCount={0}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,17 +1,20 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import GithubDbcList from '../../components/GithubDbcList';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import GithubDbcList from "../../components/GithubDbcList";
import React from "react";
import { shallow, mount, render } from "enzyme";
import OpenDbc from '../../api/OpenDbc';
import OpenDbc from "../../api/OpenDbc";
test('GithubDbcList successfully mounts with minimal default props', () => {
const openDbcClient = new OpenDbc(null);
test("GithubDbcList successfully mounts with minimal default props", () => {
const openDbcClient = new OpenDbc(null);
const component = shallow(<GithubDbcList
onDbcLoaded={ () => {} }
repo="commaai/opendbc"
openDbcClient={ openDbcClient } />);
expect(component.exists()).toBe(true);
const component = shallow(
<GithubDbcList
onDbcLoaded={() => {}}
repo="commaai/opendbc"
openDbcClient={openDbcClient}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,23 +1,26 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import HLS from '../../components/HLS';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import HLS from "../../components/HLS";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('HLS successfully mounts with minimal default props', () => {
const component = shallow(<HLS
source={'http://comma.ai'}
startTime={0}
playbackSpeed={1}
onVideoElementAvailable={() => {}}
playing={false}
onClick={() => {}}
onLoadStart={() => {}}
onLoadEnd={() => {}}
onUserSeek={() => {}}
onPlaySeek={() => {}}
segmentProgress={() => {}}
shouldRestart={false}
onRestart={() => {}} />);
expect(component.exists()).toBe(true);
test("HLS successfully mounts with minimal default props", () => {
const component = shallow(
<HLS
source={"http://comma.ai"}
startTime={0}
playbackSpeed={1}
onVideoElementAvailable={() => {}}
playing={false}
onClick={() => {}}
onLoadStart={() => {}}
onLoadEnd={() => {}}
onUserSeek={() => {}}
onPlaySeek={() => {}}
segmentProgress={() => {}}
shouldRestart={false}
onRestart={() => {}}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,18 +1,20 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import React from "react";
import { shallow, mount, render } from "enzyme";
import LoadDbcModal from '../../components/LoadDbcModal';
import OpenDbc from '../../api/OpenDbc';
import LoadDbcModal from "../../components/LoadDbcModal";
import OpenDbc from "../../api/OpenDbc";
test('LoadDbcModal successfully mounts with minimal default props', () => {
const openDbcClient = new OpenDbc(null);
const component = shallow(<LoadDbcModal
onDbcSelected={() => {}}
handleClose={() => {}}
openDbcClient={openDbcClient}
loginWithGithub={<p>Login with github</p>}
/>);
expect(component.exists()).toBe(true);
test("LoadDbcModal successfully mounts with minimal default props", () => {
const openDbcClient = new OpenDbc(null);
const component = shallow(
<LoadDbcModal
onDbcSelected={() => {}}
handleClose={() => {}}
openDbcClient={openDbcClient}
loginWithGithub={<p>Login with github</p>}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,10 +1,10 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import LoadingBar from '../../components/LoadingBar';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import LoadingBar from "../../components/LoadingBar";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('LoadingBar successfully mounts with minimal default props', () => {
const component = shallow(<LoadingBar />);
expect(component.exists()).toBe(true);
test("LoadingBar successfully mounts with minimal default props", () => {
const component = shallow(<LoadingBar />);
expect(component.exists()).toBe(true);
});

View File

@ -1,17 +1,16 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import React from "react";
import { shallow, mount, render } from "enzyme";
import MessageBytes from '../../components/MessageBytes';
import DbcUtils from '../../utils/dbc';
import DBC from '../../models/can/dbc';
import MessageBytes from "../../components/MessageBytes";
import DbcUtils from "../../utils/dbc";
import DBC from "../../models/can/dbc";
test('MessageBytes successfully mounts with minimal default props', () => {
const message = DbcUtils.createMessageSpec(new DBC(), 0, '0', 1);
const component = shallow(<MessageBytes
seekTime={0}
message={message}
live={true} />);
expect(component.exists()).toBe(true);
test("MessageBytes successfully mounts with minimal default props", () => {
const message = DbcUtils.createMessageSpec(new DBC(), 0, "0", 1);
const component = shallow(
<MessageBytes seekTime={0} message={message} live={true} />
);
expect(component.exists()).toBe(true);
});

View File

@ -1,30 +1,33 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import Meta from '../../components/Meta';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Meta from "../../components/Meta";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('Meta successfully mounts with minimal default props', () => {
const component = shallow(<Meta url={null}
messages={{}}
selectedMessages={[]}
updateSelectedMessages={() => {}}
showEditMessageModal={() => {}}
currentParts={[]}
onMessageSelected={() => {}}
onMessageUnselected={() => {}}
showLoadDbc={() => {}}
showSaveDbc={() => {}}
dbcFilename={null}
dbcLastSaved={null}
dongleId={null}
name={null}
route={null}
seekTime={0}
seekIndex={0}
maxByteStateChangeCount={0}
isDemo={false}
live={true}
/>);
expect(component.exists()).toBe(true);
test("Meta successfully mounts with minimal default props", () => {
const component = shallow(
<Meta
url={null}
messages={{}}
selectedMessages={[]}
updateSelectedMessages={() => {}}
showEditMessageModal={() => {}}
currentParts={[]}
onMessageSelected={() => {}}
onMessageUnselected={() => {}}
showLoadDbc={() => {}}
showSaveDbc={() => {}}
dbcFilename={null}
dbcLastSaved={null}
dongleId={null}
name={null}
route={null}
seekTime={0}
seekIndex={0}
maxByteStateChangeCount={0}
isDemo={false}
live={true}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,10 +1,10 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import Modal from '../../components/Modal';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Modal from "../../components/Modal";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('Modal successfully mounts with minimal default props', () => {
const component = shallow(<Modal />);
expect(component.exists()).toBe(true);
test("Modal successfully mounts with minimal default props", () => {
const component = shallow(<Modal />);
expect(component.exists()).toBe(true);
});

View File

@ -1,12 +1,12 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import PartSelector from '../../components/PartSelector';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import PartSelector from "../../components/PartSelector";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('PartSelector successfully mounts with minimal default props', () => {
const component = shallow(<PartSelector
onPartChange={ () => {} }
partsCount={ 0 } />);
expect(component.exists()).toBe(true);
test("PartSelector successfully mounts with minimal default props", () => {
const component = shallow(
<PartSelector onPartChange={() => {}} partsCount={0} />
);
expect(component.exists()).toBe(true);
});

View File

@ -1,10 +1,10 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import PlayButton from '../../components/PlayButton';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import PlayButton from "../../components/PlayButton";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('PlayButton successfully mounts with minimal default props', () => {
const component = shallow(<PlayButton />);
expect(component.exists()).toBe(true);
test("PlayButton successfully mounts with minimal default props", () => {
const component = shallow(<PlayButton />);
expect(component.exists()).toBe(true);
});

View File

@ -1,21 +1,24 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import RouteSeeker from '../../components/RouteSeeker';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import RouteSeeker from "../../components/RouteSeeker";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('RouteSeeker successfully mounts with minimal default props', () => {
const component = shallow(<RouteSeeker
nearestFrameTime={0}
segmentProgress={() => {}}
secondsLoaded={0}
segmentIndices={[]}
onUserSeek={() => {}}
onPlaySeek={() => {}}
videoElement={null}
onPlay={() => {}}
onPause={() => {}}
playing={false}
ratioTime={() => {}} />);
expect(component.exists()).toBe(true);
test("RouteSeeker successfully mounts with minimal default props", () => {
const component = shallow(
<RouteSeeker
nearestFrameTime={0}
segmentProgress={() => {}}
secondsLoaded={0}
segmentIndices={[]}
onUserSeek={() => {}}
onPlaySeek={() => {}}
videoElement={null}
onPlay={() => {}}
onPause={() => {}}
playing={false}
ratioTime={() => {}}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,9 +1,9 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import RouteVideoSync from '../../components/RouteVideoSync';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
import RouteVideoSync from "../../components/RouteVideoSync";
import React from "react";
import { shallow, mount, render } from "enzyme";
import { StyleSheetTestUtils } from "aphrodite";
// Prevents style injection from firing after test finishes
// and jsdom is torn down.
@ -14,22 +14,25 @@ afterEach(() => {
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
});
test('RouteVideoSync successfully mounts with minimal default props', () => {
const component = shallow(<RouteVideoSync
message={null}
secondsLoaded={0}
startOffset={0}
seekIndex={0}
userSeekIndex={0}
playing={false}
url={'http://comma.ai'}
canFrameOffset={0}
firstCanTime={0}
onVideoClick={() => {}}
onPlaySeek={() => {}}
onUserSeek={() => {}}
onPlay={() => {}}
onPause={() => {}}
userSeekTime={0} />);
expect(component.exists()).toBe(true);
test("RouteVideoSync successfully mounts with minimal default props", () => {
const component = shallow(
<RouteVideoSync
message={null}
secondsLoaded={0}
startOffset={0}
seekIndex={0}
userSeekIndex={0}
playing={false}
url={"http://comma.ai"}
canFrameOffset={0}
firstCanTime={0}
onVideoClick={() => {}}
onPlaySeek={() => {}}
onUserSeek={() => {}}
onPlay={() => {}}
onPause={() => {}}
userSeekTime={0}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,23 +1,25 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import React from "react";
import { shallow, mount, render } from "enzyme";
import SaveDbcModal from '../../components/SaveDbcModal';
import OpenDbc from '../../api/OpenDbc';
import DBC from '../../models/can/dbc';
import SaveDbcModal from "../../components/SaveDbcModal";
import OpenDbc from "../../api/OpenDbc";
import DBC from "../../models/can/dbc";
test('SaveDbcModal successfully mounts with minimal default props', () => {
const openDbcClient = new OpenDbc(null);
const dbc = new DBC();
const component = shallow(<SaveDbcModal
dbc={dbc}
sourceDbcFilename={''}
onDbcSaved={() => {}}
handleClose={() => {}}
openDbcClient={openDbcClient}
hasGithubAuth={false}
loginWithGithub={<p>Login with github</p>}
/>);
expect(component.exists()).toBe(true);
test("SaveDbcModal successfully mounts with minimal default props", () => {
const openDbcClient = new OpenDbc(null);
const dbc = new DBC();
const component = shallow(
<SaveDbcModal
dbc={dbc}
sourceDbcFilename={""}
onDbcSaved={() => {}}
handleClose={() => {}}
openDbcClient={openDbcClient}
hasGithubAuth={false}
loginWithGithub={<p>Login with github</p>}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,21 +1,23 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import SignalLegend from '../../components/SignalLegend';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import SignalLegend from "../../components/SignalLegend";
import React from "react";
import { shallow, mount, render } from "enzyme";
test('SignalLegend successfully mounts with minimal default props', () => {
const component = shallow(<SignalLegend
signals={{}}
signalStyles={{}}
highlightedSignal={null}
onSignalHover={() => {}}
onSignalHoverEnd={() => {}}
onTentativeSignalChange={() => {}}
onSignalChange={() => {}}
onSignalRemove={() => {}}
onSignalPlotChange={() => {}}
plottedSignals={[]}
/>);
expect(component.exists()).toBe(true);
test("SignalLegend successfully mounts with minimal default props", () => {
const component = shallow(
<SignalLegend
signals={{}}
signalStyles={{}}
highlightedSignal={null}
onSignalHover={() => {}}
onSignalHoverEnd={() => {}}
onTentativeSignalChange={() => {}}
onSignalChange={() => {}}
onSignalRemove={() => {}}
onSignalPlotChange={() => {}}
plottedSignals={[]}
/>
);
expect(component.exists()).toBe(true);
});

View File

@ -1,8 +1,8 @@
import SignalLegendEntry from '../../components/SignalLegendEntry';
import Signal from '../../models/can/signal';
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import {StyleSheetTestUtils} from 'aphrodite';
import SignalLegendEntry from "../../components/SignalLegendEntry";
import Signal from "../../models/can/signal";
import React from "react";
import { shallow, mount, render } from "enzyme";
import { StyleSheetTestUtils } from "aphrodite";
// Prevents style injection from firing after test finishes
// and jsdom is torn down.
@ -14,100 +14,122 @@ afterEach(() => {
});
function createSignalLegendEntry(props) {
let signal = props.signal,
onSignalChange = props.onSignalChange,
onTentativeSignalChange = props.onTentativeSignalChange;
if(signal === undefined) {
signal = new Signal({name: 'NEW_SIGNAL'});
}
if(onSignalChange === undefined) {
onSignalChange = () => {};
}
if(onTentativeSignalChange === undefined) {
onTentativeSignalChange = () => {};
}
let signal = props.signal,
onSignalChange = props.onSignalChange,
onTentativeSignalChange = props.onTentativeSignalChange;
if (signal === undefined) {
signal = new Signal({ name: "NEW_SIGNAL" });
}
if (onSignalChange === undefined) {
onSignalChange = () => {};
}
if (onTentativeSignalChange === undefined) {
onTentativeSignalChange = () => {};
}
return shallow(<SignalLegendEntry
highlightedStyle={null}
signal={signal}
onSignalChange={onSignalChange}
onTentativeSignalChange={onTentativeSignalChange}
/>);
return shallow(
<SignalLegendEntry
highlightedStyle={null}
signal={signal}
onSignalChange={onSignalChange}
onTentativeSignalChange={onTentativeSignalChange}
/>
);
}
test('a little endian signal spanning one byte should adjust its startBit switching to big endian, preserving its bit coverage', () => {
const signal = new Signal({name: 'signal',
startBit: 0,
size: 8,
isLittleEndian: true});
test("a little endian signal spanning one byte should adjust its startBit switching to big endian, preserving its bit coverage", () => {
const signal = new Signal({
name: "signal",
startBit: 0,
size: 8,
isLittleEndian: true
});
const component = createSignalLegendEntry({signal});
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
component.instance().updateField(endiannessFieldSpec, false);
const component = createSignalLegendEntry({ signal });
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
"isLittleEndian"
);
component.instance().updateField(endiannessFieldSpec, false);
const signalEdited = component.state('signalEdited');
expect(signalEdited.isLittleEndian).toBe(false);
expect(signalEdited.startBit).toBe(7);
expect(signalEdited.size).toBe(8);
const signalEdited = component.state("signalEdited");
expect(signalEdited.isLittleEndian).toBe(false);
expect(signalEdited.startBit).toBe(7);
expect(signalEdited.size).toBe(8);
});
test('a big endian signal spanning two bytes should should adjust its startBit switching to little endian, preserving its bit coverage', () => {
const signal = new Signal({name: 'signal',
startBit: 7,
size: 8,
isLittleEndian: false});
const component = createSignalLegendEntry({signal});
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
component.instance().updateField(endiannessFieldSpec, true);
test("a big endian signal spanning two bytes should should adjust its startBit switching to little endian, preserving its bit coverage", () => {
const signal = new Signal({
name: "signal",
startBit: 7,
size: 8,
isLittleEndian: false
});
const component = createSignalLegendEntry({ signal });
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
"isLittleEndian"
);
component.instance().updateField(endiannessFieldSpec, true);
const signalEdited = component.state('signalEdited');
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(0);
expect(signalEdited.size).toBe(8);
const signalEdited = component.state("signalEdited");
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(0);
expect(signalEdited.size).toBe(8);
});
test("a big endian signal spanning one and a half bytes should adjust its startBit switching to little endian, preserving the first byte's coverage", () => {
const signal = new Signal({name: 'signal',
startBit: 7,
size: 12,
isLittleEndian: false});
const signal = new Signal({
name: "signal",
startBit: 7,
size: 12,
isLittleEndian: false
});
const component = createSignalLegendEntry({signal});
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
component.instance().updateField(endiannessFieldSpec, true);
const component = createSignalLegendEntry({ signal });
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
"isLittleEndian"
);
component.instance().updateField(endiannessFieldSpec, true);
const signalEdited = component.state('signalEdited');
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(0);
expect(signalEdited.size).toBe(12);
const signalEdited = component.state("signalEdited");
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(0);
expect(signalEdited.size).toBe(12);
});
test('a little endian signal spanning 3 bits on one byte should adjust its startBit switching to big endian, preserving its bit coverage', () => {
const signal = new Signal({name: 'signal',
startBit: 13,
size: 3,
isLittleEndian: true});
const component = createSignalLegendEntry({signal});
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
component.instance().updateField(endiannessFieldSpec, false);
test("a little endian signal spanning 3 bits on one byte should adjust its startBit switching to big endian, preserving its bit coverage", () => {
const signal = new Signal({
name: "signal",
startBit: 13,
size: 3,
isLittleEndian: true
});
const component = createSignalLegendEntry({ signal });
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
"isLittleEndian"
);
component.instance().updateField(endiannessFieldSpec, false);
const signalEdited = component.state('signalEdited');
expect(signalEdited.isLittleEndian).toBe(false);
expect(signalEdited.startBit).toBe(15);
expect(signalEdited.size).toBe(3);
const signalEdited = component.state("signalEdited");
expect(signalEdited.isLittleEndian).toBe(false);
expect(signalEdited.startBit).toBe(15);
expect(signalEdited.size).toBe(3);
});
test('a big endian signal spanning 3 bytes on one byte should adjust its startBit switching to little endian, preserving its bit coverage', () => {
const signal = new Signal({name: 'signal',
startBit: 15,
size: 3,
isLittleEndian: false});
const component = createSignalLegendEntry({signal});
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName('isLittleEndian');
component.instance().updateField(endiannessFieldSpec, true);
test("a big endian signal spanning 3 bytes on one byte should adjust its startBit switching to little endian, preserving its bit coverage", () => {
const signal = new Signal({
name: "signal",
startBit: 15,
size: 3,
isLittleEndian: false
});
const component = createSignalLegendEntry({ signal });
const endiannessFieldSpec = SignalLegendEntry.fieldSpecForName(
"isLittleEndian"
);
component.instance().updateField(endiannessFieldSpec, true);
const signalEdited = component.state('signalEdited');
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(13);
expect(signalEdited.size).toBe(3);
});
const signalEdited = component.state("signalEdited");
expect(signalEdited.isLittleEndian).toBe(true);
expect(signalEdited.startBit).toBe(13);
expect(signalEdited.size).toBe(3);
});

View File

@ -1,38 +1,45 @@
// appendNewGraphData(plottedSignals, graphData, messages) {
global.__JEST__ = 1;
import GraphData from '../../models/graph-data';
import Signal from '../../models/can/signal';
import DBC from '../../models/can/dbc';
import DbcUtils from '../../utils/dbc';
import GraphData from "../../models/graph-data";
import Signal from "../../models/can/signal";
import DBC from "../../models/can/dbc";
import DbcUtils from "../../utils/dbc";
function appendMockGraphData(existingGraphData, entryCount = 1) {
const dbc = new DBC();
const signal = new Signal({name: 'NEW_SIGNAL_1'});
dbc.setSignals(0, {[signal.name]: signal});
const message = DbcUtils.createMessageSpec(dbc, 0, '0', 0);
// time, relTime, data, byteStateChangeTimes) {
message.entries = Array(entryCount).fill(DbcUtils.createMessageEntry(dbc, 0, 0, 0, Buffer.alloc(8), []));
const messages = {[message.id]: message};
const dbc = new DBC();
const signal = new Signal({ name: "NEW_SIGNAL_1" });
dbc.setSignals(0, { [signal.name]: signal });
const message = DbcUtils.createMessageSpec(dbc, 0, "0", 0);
// time, relTime, data, byteStateChangeTimes) {
message.entries = Array(entryCount).fill(
DbcUtils.createMessageEntry(dbc, 0, 0, 0, Buffer.alloc(8), [])
);
const messages = { [message.id]: message };
const plottedSignals = [[{signalUid: signal.uid, messageId: '0'}]];
const plottedSignals = [[{ signalUid: signal.uid, messageId: "0" }]];
return GraphData.appendNewGraphData(plottedSignals, existingGraphData, messages, 0);
return GraphData.appendNewGraphData(
plottedSignals,
existingGraphData,
messages,
0
);
}
test('GraphData.appendNewGraphData adds messages to empty GraphData array', () => {
const graphData = appendMockGraphData([[]]);
expect(graphData.length).toEqual(1); // 1 plot
expect(graphData[0].length).toEqual(1); // 1 message entry
expect(graphData[0][0].x).toEqual(0); // message entry X value corresponds to provided time in createMessageEntry
test("GraphData.appendNewGraphData adds messages to empty GraphData array", () => {
const graphData = appendMockGraphData([[]]);
expect(graphData.length).toEqual(1); // 1 plot
expect(graphData[0].length).toEqual(1); // 1 message entry
expect(graphData[0][0].x).toEqual(0); // message entry X value corresponds to provided time in createMessageEntry
});
test('GraphData.appendNewGraphData does not change graph data when entries are unchanged', () => {
let graphData = [[]];
for(let i = 0; i < 100; i++) {
graphData = appendMockGraphData(graphData);
}
test("GraphData.appendNewGraphData does not change graph data when entries are unchanged", () => {
let graphData = [[]];
for (let i = 0; i < 100; i++) {
graphData = appendMockGraphData(graphData);
}
expect(graphData.length).toEqual(1);
expect(graphData[0].length).toEqual(1);
});
expect(graphData.length).toEqual(1);
expect(graphData[0].length).toEqual(1);
});

View File

@ -1,23 +1,22 @@
import Panda from '../../api/panda';
import Panda from "../../api/panda";
function arrayBufferFromHex(hex) {
const buffer = Buffer.from(hex, 'hex');
const arrayBuffer = new ArrayBuffer(buffer.length);
const view = new Uint8Array(arrayBuffer);
for(let i = 0; i < buffer.length; i++) {
view[i] = buffer[i];
}
return arrayBuffer;
const buffer = Buffer.from(hex, "hex");
const arrayBuffer = new ArrayBuffer(buffer.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; i++) {
view[i] = buffer[i];
}
return arrayBuffer;
}
test('parseCanBuffer correctly parses a message', () => {
const panda = new Panda();
// 16 byte buffer
test("parseCanBuffer correctly parses a message", () => {
const panda = new Panda();
// 16 byte buffer
const arrayBuffer = arrayBufferFromHex('abababababababababababababababab');
const arrayBuffer = arrayBufferFromHex("abababababababababababababababab");
const messages = panda.parseCanBuffer(arrayBuffer);
expect(messages.length).toEqual(1)
expect(messages[0]).toEqual([1373, 43947, 'abababababababab', 10]);
const messages = panda.parseCanBuffer(arrayBuffer);
expect(messages.length).toEqual(1);
expect(messages[0]).toEqual([1373, 43947, "abababababababab", 10]);
});

View File

@ -1,4 +1,4 @@
export const ACURA_DBC= `VERSION ""
export const ACURA_DBC = `VERSION ""
NS_ :
@ -318,4 +318,4 @@ VAL_ 506 CHIME 4 "double_chime" 3 "single_chime" 2 "continuous_chime" 1 "repeati
VAL_ 506 FCW 3 "fcw" 2 "fcw" 1 "fcw" 0 "no_fcw" ;
VAL_ 780 HUD_LEAD 3 "no_car" 2 "solid_car" 1 "dashed_car" 0 "no_car" ;
VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ;
`;
`;

View File

@ -404,4 +404,4 @@ VAL_ 904 MCU_clusterReadyForDrive 0 "NO_SNA" 1 "YES" ;
VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
`;
`;

View File

@ -1,8 +1,8 @@
import {shade} from '../../utils/color';
import { shade } from "../../utils/color";
test('Shade darkens rgb white (255,255,255)', () => {
const rgb = [255,255,255];
const darkenRgb = shade(rgb, -0.5);
test("Shade darkens rgb white (255,255,255)", () => {
const rgb = [255, 255, 255];
const darkenRgb = shade(rgb, -0.5);
expect(darkenRgb).toEqual([128,128,128]);
});
expect(darkenRgb).toEqual([128, 128, 128]);
});

View File

@ -1,63 +1,70 @@
global.__JEST__ = 1
global.__JEST__ = 1;
import DbcUtils from '../../utils/dbc';
import DBC from '../../models/can/dbc';
import Signal from '../../models/can/signal';
import DbcUtils from "../../utils/dbc";
import DBC from "../../models/can/dbc";
import Signal from "../../models/can/signal";
// want to mock pandareader and test processStreamedCanMessages
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from('abababababababab', 'hex'), 1];
const SAMPLE_MESSAGE_ID = '1:10';
const SAMPLE_MESSAGE = [0x10, 0, Buffer.from("abababababababab", "hex"), 1];
const SAMPLE_MESSAGE_ID = "1:10";
function expectSampleMessageFieldsPreserved(messages, frame) {
const [address, busTime, data, source] = SAMPLE_MESSAGE;
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(Array(8).fill(0));
const [address, busTime, data, source] = SAMPLE_MESSAGE;
expect(messages[SAMPLE_MESSAGE_ID].address).toEqual(address);
expect(messages[SAMPLE_MESSAGE_ID].id).toEqual(SAMPLE_MESSAGE_ID);
expect(messages[SAMPLE_MESSAGE_ID].bus).toEqual(source);
expect(messages[SAMPLE_MESSAGE_ID].frame).toEqual(frame);
expect(messages[SAMPLE_MESSAGE_ID].byteStateChangeCounts).toEqual(
Array(8).fill(0)
);
}
// function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
function addMessages(messages, message, dbc, n) {
const firstCanTime = 0;
message = [...message];
let nextMessage = () => {message[1] = message[1] + 1; return message};
const firstCanTime = 0;
message = [...message];
let nextMessage = () => {
message[1] = message[1] + 1;
return message;
};
for(let i = 0; i < n; i++) {
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
}
for (let i = 0; i < n; i++) {
DbcUtils.addCanMessage(nextMessage(), dbc, firstCanTime, messages, {}, {});
}
}
test('addCanMessage should add raw can message with empty dbc', () => {
const messages = {};
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
test("addCanMessage should add raw can message with empty dbc", () => {
const messages = {};
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 1);
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
expectSampleMessageFieldsPreserved(messages);
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(1);
expectSampleMessageFieldsPreserved(messages);
});
test('addCanMessage should add multiple raw can messages with empty dbc', () => {
const messages = {};
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
test("addCanMessage should add multiple raw can messages with empty dbc", () => {
const messages = {};
addMessages(messages, SAMPLE_MESSAGE, new DBC(), 3);
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
expectSampleMessageFieldsPreserved(messages);
expect(messages[SAMPLE_MESSAGE_ID].entries.length).toEqual(3);
expectSampleMessageFieldsPreserved(messages);
});
test('addCanMessage should add parsed can message with dbc containing message spec', () => {
const messages = {};
// create dbc with message spec and signal for sample_message
const dbc = new DBC();
dbc.createFrame(SAMPLE_MESSAGE[0]);
const signal = new Signal({name: 'NEW_SIGNAL', startBit: 0, size: 8});
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
test("addCanMessage should add parsed can message with dbc containing message spec", () => {
const messages = {};
// create dbc with message spec and signal for sample_message
const dbc = new DBC();
dbc.createFrame(SAMPLE_MESSAGE[0]);
const signal = new Signal({ name: "NEW_SIGNAL", startBit: 0, size: 8 });
dbc.addSignal(SAMPLE_MESSAGE[0], signal);
// add 1 sample_message
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
// add 1 sample_message
addMessages(messages, SAMPLE_MESSAGE, dbc, 1);
// verify message and parsed signal added
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
expect(sampleMessages.entries.length).toEqual(1);
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
expectSampleMessageFieldsPreserved(messages, dbc.messages.get(SAMPLE_MESSAGE[0]));
// verify message and parsed signal added
const sampleMessages = messages[SAMPLE_MESSAGE_ID];
expect(sampleMessages.entries.length).toEqual(1);
expect(sampleMessages.entries[0].signals[signal.name]).toEqual(0xab);
expectSampleMessageFieldsPreserved(
messages,
dbc.messages.get(SAMPLE_MESSAGE[0])
);
});

View File

@ -1,4 +1,4 @@
import DBC from './models/can/dbc';
import DBC from "./models/can/dbc";
const AcuraDbc = new DBC(`
VERSION ""

View File

@ -1,12 +1,12 @@
import GitHub from 'github-api';
import GitHub from "github-api";
import {OPENDBC_SOURCE_REPO} from '../config';
import { OPENDBC_SOURCE_REPO } from "../config";
export default class OpenDBC {
constructor(token) {
this.token = token;
this.github = new GitHub({token});
this.sourceRepo = this.github.getRepo('commaai', 'opendbc');
this.github = new GitHub({ token });
this.sourceRepo = this.github.getRepo("commaai", "opendbc");
this.githubUsername = null;
}
@ -15,11 +15,11 @@ export default class OpenDBC {
}
async getGithubUsername() {
if(this.githubUsername) {
if (this.githubUsername) {
return this.githubUsername;
} else {
const githubUsername = await this.fetchGithubUsername();
if(githubUsername) {
if (githubUsername) {
return githubUsername;
}
}
@ -28,13 +28,13 @@ export default class OpenDBC {
async fetchGithubUsername() {
try {
const user = await this.github.getUser();
if(user) {
if (user) {
const profile = await user.getProfile();
if(profile) {
if (profile) {
return profile.data.login;
}
}
} catch(e) {
} catch (e) {
return null;
}
}
@ -47,31 +47,31 @@ export default class OpenDBC {
*/
let repo;
if(repoFullName === undefined) {
if (repoFullName === undefined) {
repo = this.sourceRepo;
} else {
const [username, repoName] = repoFullName.split('/');
const [username, repoName] = repoFullName.split("/");
repo = this.github.getRepo(username, repoName);
}
try {
const response = await repo.getContents('master', '');
const response = await repo.getContents("master", "");
return response.data.map((content) => content.path);
} catch(e) {
return response.data.map(content => content.path);
} catch (e) {
return [];
}
}
async getDbcContents(dbcPath, repoFullName) {
let repo;
if(repoFullName === undefined) {
if (repoFullName === undefined) {
repo = this.sourceRepo;
} else {
const [username, repoName] = repoFullName.split('/');
const [username, repoName] = repoFullName.split("/");
repo = this.github.getRepo(username, repoName);
}
const fileContents = await repo.getContents('master', dbcPath);
const fileContents = await repo.getContents("master", dbcPath);
const rawContentsUrl = fileContents.data.download_url;
@ -81,72 +81,83 @@ export default class OpenDBC {
}
repoSourceIsOpenDbc(repoDetails) {
return repoDetails.source
&& repoDetails.source.full_name === OPENDBC_SOURCE_REPO;
return (
repoDetails.source && repoDetails.source.full_name === OPENDBC_SOURCE_REPO
);
}
async getUserOpenDbcFork() {
const githubUsername = await this.getGithubUsername();
if(!githubUsername) return null;
const githubUsername = await this.getGithubUsername();
if (!githubUsername) return null;
const openDbcFork = this.github.getRepo(githubUsername, 'opendbc');
const repoDetailResp = await openDbcFork.getDetails();
const repoDetails = repoDetailResp.data;
const openDbcFork = this.github.getRepo(githubUsername, "opendbc");
const repoDetailResp = await openDbcFork.getDetails();
const repoDetails = repoDetailResp.data;
if(this.repoSourceIsOpenDbc(repoDetails)) {
return repoDetails.full_name;
} else {
return null;
}
if (this.repoSourceIsOpenDbc(repoDetails)) {
return repoDetails.full_name;
} else {
return null;
}
}
async fork() {
const forkResponse = await this.sourceRepo.fork();
if(forkResponse.status === 202) {
return true;
} else {
return false;
}
const forkResponse = await this.sourceRepo.fork();
if (forkResponse.status === 202) {
return true;
} else {
return false;
}
}
async commitFile(repoFullName, path, contents) {
/*
/*
repo is of format username/reponame
authenciated user must have write access to repo
*/
const [user, repoName] = repoFullName.split('/');
const repo = this.github.getRepo(user, repoName);
const [user, repoName] = repoFullName.split("/");
const repo = this.github.getRepo(user, repoName);
// get HEAD reference
const refResp = await repo.getRef('heads/master');
const ref = refResp.data;
// get HEAD reference
const refResp = await repo.getRef("heads/master");
const ref = refResp.data;
// get HEAD commit sha
const headCommitResp = await repo.getCommit(ref.object.sha);
const headCommit = headCommitResp.data;
// get HEAD commit sha
const headCommitResp = await repo.getCommit(ref.object.sha);
const headCommit = headCommitResp.data;
// get HEAD tree
const headTreeResp = await repo.getTree(headCommit.tree.sha);
const headTree = headTreeResp.data;
// get HEAD tree
const headTreeResp = await repo.getTree(headCommit.tree.sha);
const headTree = headTreeResp.data;
// create new tree
const tree = [{
mode: '100644',
path: path,
type: 'blob',
content: contents,
}];
// create new tree
const tree = [
{
mode: "100644",
path: path,
type: "blob",
content: contents
}
];
const createTreeResp = await repo.createTree(tree, headTree.sha);
const createdTree = createTreeResp.data;
const createTreeResp = await repo.createTree(tree, headTree.sha);
const createdTree = createTreeResp.data;
// commit
const commitResp = await repo.commit(headCommit.sha, createdTree.sha, 'OpenDBC updates');
const commit = commitResp.data;
// commit
const commitResp = await repo.commit(
headCommit.sha,
createdTree.sha,
"OpenDBC updates"
);
const commit = commitResp.data;
// update HEAD
const updateHeadResp = await repo.updateHead('heads/master', commit.sha, false);
// update HEAD
const updateHeadResp = await repo.updateHead(
"heads/master",
commit.sha,
false
);
return updateHeadResp.status === 200;
return updateHeadResp.status === 200;
}
}

View File

@ -1,32 +1,38 @@
import NumpyLoader from '../utils/loadnpy';
import NumpyLoader from "../utils/loadnpy";
export async function fetchCanTimes(base, part) {
const url = base+"/Log/"+part+"/can/t";
const url = base + "/Log/" + part + "/can/t";
const canData = await NumpyLoader.promise(url);
const canData = await NumpyLoader.promise(url);
return canData.data;
return canData.data;
}
export async function fetchCanPart(base, part) {
var urls = [ base+"/Log/"+part+"/can/t",
base+"/Log/"+part+"/can/src",
base+"/Log/"+part+"/can/address",
base+"/Log/"+part+"/can/data"];
var urls = [
base + "/Log/" + part + "/can/t",
base + "/Log/" + part + "/can/src",
base + "/Log/" + part + "/can/address",
base + "/Log/" + part + "/can/data"
];
var messages = {};
let canData = null;
try {
canData = await Promise.all(urls.map(NumpyLoader.promise));
} catch (e) {
console.log('this is a 404 workaround that is hacky', e)
return {times: [],
sources: [],
addresses: [],
datas: []}
}
return {times: canData[0].data,
sources: canData[1].data,
addresses: canData[2].data,
datas: canData[3].data};
}
var messages = {};
let canData = null;
try {
canData = await Promise.all(urls.map(NumpyLoader.promise));
} catch (e) {
console.log("this is a 404 workaround that is hacky", e);
return {
times: [],
sources: [],
addresses: [],
datas: []
};
}
return {
times: canData[0].data,
sources: canData[1].data,
addresses: canData[2].data,
datas: canData[3].data
};
}

View File

@ -1,19 +1,22 @@
import Cookies from 'js-cookie';
import Cookies from "js-cookie";
import {COMMA_ACCESS_TOKEN_COOKIE, COMMA_OAUTH_REDIRECT_COOKIE} from '../config';
import {
COMMA_ACCESS_TOKEN_COOKIE,
COMMA_OAUTH_REDIRECT_COOKIE
} from "../config";
function getCommaAccessToken() {
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
}
function isAuthenticated() {
return getCommaAccessToken() !== undefined;
return getCommaAccessToken() !== undefined;
}
function authUrl() {
Cookies.set(COMMA_OAUTH_REDIRECT_COOKIE, window.location.href);
Cookies.set(COMMA_OAUTH_REDIRECT_COOKIE, window.location.href);
return 'https://community.comma.ai/ucp.php?mode=login&login=external&oauth_service=google';
return "https://community.comma.ai/ucp.php?mode=login&login=external&oauth_service=google";
}
export default {getCommaAccessToken, isAuthenticated, authUrl};
export default { getCommaAccessToken, isAuthenticated, authUrl };

View File

@ -1,11 +1,13 @@
import {GITHUB_CLIENT_ID, GITHUB_REDIRECT_URL} from '../config';
import {objToQuery} from '../utils/url';
import { GITHUB_CLIENT_ID, GITHUB_REDIRECT_URL } from "../config";
import { objToQuery } from "../utils/url";
export function authorizeUrl(route) {
const params = {client_id: GITHUB_CLIENT_ID,
redirect_uri: GITHUB_REDIRECT_URL,
scope: 'user:email public_repo',
state: JSON.stringify({route})};
const params = {
client_id: GITHUB_CLIENT_ID,
redirect_uri: GITHUB_REDIRECT_URL,
scope: "user:email public_repo",
state: JSON.stringify({ route })
};
return `http://github.com/login/oauth/authorize?${objToQuery(params)}`;
}

View File

@ -1,24 +1,26 @@
import DBC from '../models/can/dbc';
import DBC from "../models/can/dbc";
export function fetchPersistedDbc(routeName) {
const maybeDbc = window.localStorage.getItem(routeName);
if(maybeDbc !== null) {
const {dbcFilename, dbcText} = JSON.parse(maybeDbc);
if (maybeDbc !== null) {
const { dbcFilename, dbcText } = JSON.parse(maybeDbc);
const dbc = new DBC(dbcText);
return {dbc, dbcText, dbcFilename};
return { dbc, dbcText, dbcFilename };
} else return null;
}
export function persistDbc(routeName, {dbcFilename, dbc}) {
const dbcJson = JSON.stringify({dbcFilename,
dbcText: dbc.text()});
export function persistDbc(routeName, { dbcFilename, dbc }) {
const dbcJson = JSON.stringify({
dbcFilename,
dbcText: dbc.text()
});
window.localStorage.setItem(routeName, dbcJson);
}
const GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY = 'gh_auth_token';
const GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY = "gh_auth_token";
export function fetchPersistedGithubAuthToken() {
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
return window.localStorage.getItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY);
}
export function unpersistGithubAuthToken() {
@ -26,5 +28,5 @@ export function unpersistGithubAuthToken() {
}
export function persistGithubAuthToken(token) {
return window.localStorage.setItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY, token);
}
return window.localStorage.setItem(GITHUB_AUTH_TOKEN_LOCALSTORAGE_KEY, token);
}

View File

@ -1,66 +1,76 @@
import Panda from './panda';
import Panda from "./panda";
export default class PandaReader {
static ERROR_NO_DEVICE_SELECTED = 8;
static ERROR_NO_DEVICE_SELECTED = 8;
constructor() {
this.panda = new Panda();
this.isReading = false;
this.onMessagesReceived = () => {};
this.callbackQueue = [];
this.callbackQueueTimer = null;
constructor() {
this.panda = new Panda();
this.isReading = false;
this.onMessagesReceived = () => {};
this.callbackQueue = [];
this.callbackQueueTimer = null;
this.readLoop = this.readLoop.bind(this);
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
}
this.readLoop = this.readLoop.bind(this);
this.flushCallbackQueue = this.flushCallbackQueue.bind(this);
this._flushCallbackQueue = this._flushCallbackQueue.bind(this);
connect() {
return this.panda.connect();
}
setOnMessagesReceivedCallback(callback) {
this.onMessagesReceived = callback;
}
stopReadLoop() {
this.isReading = false;
window.cancelAnimationFrame(this.callbackQueueTimer);
}
_flushCallbackQueue() {
const messages = this.callbackQueue.reduce(
(arr, messages) => arr.concat(messages),
[]
);
this.onMessagesReceived(messages);
this.callbackQueue = [];
}
flushCallbackQueue() {
if (this.callbackQueue.length > 0) {
this._flushCallbackQueue();
}
connect() {
return this.panda.connect();
this.callbackQueueTimer = window.requestAnimationFrame(
this.flushCallbackQueue
);
}
readLoop() {
if (!this.isReading) {
this.isReading = true;
// this.flushCallbackQueueTimer = wi
this.callbackQueueTimer = window.requestAnimationFrame(
this.flushCallbackQueue,
30
);
}
setOnMessagesReceivedCallback(callback) {
this.onMessagesReceived = callback;
}
stopReadLoop() {
this.isReading = false;
window.cancelAnimationFrame(this.callbackQueueTimer);
}
_flushCallbackQueue() {
const messages = this.callbackQueue.reduce((arr, messages) => arr.concat(messages), [])
this.onMessagesReceived(messages);
this.callbackQueue = [];
}
flushCallbackQueue() {
if(this.callbackQueue.length > 0) {
this._flushCallbackQueue();
}
this.callbackQueueTimer = window.requestAnimationFrame(this.flushCallbackQueue);
}
readLoop() {
if(!this.isReading) {
this.isReading = true;
// this.flushCallbackQueueTimer = wi
this.callbackQueueTimer = window.requestAnimationFrame(this.flushCallbackQueue, 30);
this.panda.canRecv().then(
messages => {
if (this.isReading && messages.canMessages.length > 0) {
this.callbackQueue.push(messages);
}
this.panda.canRecv().then(messages => {
if(this.isReading && messages.canMessages.length > 0) {
this.callbackQueue.push(messages);
}
this.readLoop();
},
error => {
if (this.isReading) {
console.log("canRecv error", error);
this.readLoop();
}, error => {
if(this.isReading) {
console.log('canRecv error', error);
this.readLoop();
}
});
}
}
}
);
}
}

View File

@ -1,5 +1,5 @@
import CloudLog from '../logging/CloudLog';
require('core-js/fn/string/pad-end');
import CloudLog from "../logging/CloudLog";
require("core-js/fn/string/pad-end");
const PANDA_VENDOR_ID = 0xbbaa;
const PANDA_PRODUCT_ID = 0xddcc;
@ -7,73 +7,93 @@ const PANDA_PRODUCT_ID = 0xddcc;
const BUFFER_SIZE = 0x10 * 256;
export default class Panda {
constructor() {
this.device = null;
constructor() {
this.device = null;
}
connect() {
// Must be called via a mouse click handler, per Chrome restrictions.
return navigator.usb
.requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] })
.then(device => {
this.device = device;
return device.open();
})
.then(() => this.device.selectConfiguration(1))
.then(() => this.device.claimInterface(0));
}
async health() {
const controlParams = {
requestType: "vendor",
recipient: "device",
request: 0xd2,
value: 0,
index: 0
};
try {
return await this.device.controlTransferIn(controlParams, 13);
} catch (err) {
CloudLog.error({ event: "Panda.health failed", error: err });
}
}
parseCanBuffer(buffer) {
const messages = [];
for (let i = 0; i < buffer.byteLength; i += 0x10) {
const dat = buffer.slice(i, i + 0x10);
const datView = Buffer.from(dat);
const f1 = datView.readInt32LE(0),
f2 = datView.readInt32LE(4);
const address = f1 >>> 21;
const busTime = f2 >>> 16;
const data = new Buffer(dat.slice(8, 8 + (f2 & 0xf)));
const source = (f2 >> 4) & 0xf & 0xff;
messages.push([
address,
busTime,
data.toString("hex").padEnd(16, "0"),
source
]);
}
connect() {
// Must be called via a mouse click handler, per Chrome restrictions.
return navigator.usb.requestDevice({ filters: [{ vendorId: PANDA_VENDOR_ID }] })
.then(device => {
this.device = device;
return device.open();
})
.then(() => this.device.selectConfiguration(1))
.then(() => this.device.claimInterface(0));
return messages;
}
async mockCanRecv() {
const promise = new Promise(resolve =>
setTimeout(
() =>
resolve({
time: performance.now() / 1000,
canMessages: [[0, Math.random() * 65000, "".padEnd(16, "0"), 0]]
}),
100
)
);
return await promise;
}
async canRecv() {
let result = null,
receiptTime = null;
while (result === null) {
try {
result = await this.device.transferIn(1, BUFFER_SIZE);
receiptTime = performance.now() / 1000;
} catch (err) {
console.warn("can_recv failed, retrying");
}
}
async health() {
const controlParams = { requestType: 'vendor',
recipient: 'device',
request: 0xd2,
value: 0,
index: 0 };
try {
return await this.device.controlTransferIn(controlParams, 13);
} catch(err) {
CloudLog.error({event: 'Panda.health failed', 'error': err});
}
}
parseCanBuffer(buffer) {
const messages = [];
for(let i = 0; i < buffer.byteLength; i+=0x10) {
const dat = buffer.slice(i, i + 0x10);
const datView = Buffer.from(dat);
const f1 = datView.readInt32LE(0), f2 = datView.readInt32LE(4);
const address = f1 >>> 21;
const busTime = (f2 >>> 16);
const data = new Buffer(dat.slice(8, 8 + (f2 & 0xF)));
const source = ((f2 >> 4) & 0xF) & 0xFF;
messages.push([address, busTime, data.toString('hex').padEnd(16, '0'), source]);
}
return messages;
}
async mockCanRecv() {
const promise = new Promise((resolve) =>
setTimeout(() => resolve({time: performance.now() / 1000,
canMessages: [[0, Math.random() * 65000, ''.padEnd(16, '0'), 0]]}), 100));
return await promise;
}
async canRecv() {
let result = null, receiptTime = null;
while(result === null) {
try {
result = await this.device.transferIn(1, BUFFER_SIZE);
receiptTime = performance.now() / 1000;
} catch(err) {
console.warn('can_recv failed, retrying');
}
}
return {time: receiptTime, canMessages: this.parseCanBuffer(result.data.buffer)};
}
return {
time: receiptTime,
canMessages: this.parseCanBuffer(result.data.buffer)
};
}
}

View File

@ -1,11 +1,11 @@
import Cookies from 'js-cookie';
import Moment from 'moment';
import CommaAuth from './comma-auth';
import Cookies from "js-cookie";
import Moment from "moment";
import CommaAuth from "./comma-auth";
const ROUTES_ENDPOINT = 'https://api.commadotai.com/v1/{dongleId}/routes/';
const ROUTES_ENDPOINT = "https://api.commadotai.com/v1/{dongleId}/routes/";
function momentizeTimes(routes) {
for(let routeName in routes) {
for (let routeName in routes) {
routes[routeName].start_time = Moment(routes[routeName].start_time);
routes[routeName].end_time = Moment(routes[routeName].end_time);
}
@ -13,35 +13,34 @@ function momentizeTimes(routes) {
}
export async function fetchRoutes(dongleId) {
// will throw errors from fetch() on HTTP failure
// will throw errors from fetch() on HTTP failure
if(dongleId === undefined) {
dongleId = 'me';
if (dongleId === undefined) {
dongleId = "me";
}
const accessToken = CommaAuth.getCommaAccessToken();
if (accessToken) {
const endpoint = ROUTES_ENDPOINT.replace("{dongleId}", dongleId);
const headers = new Headers();
headers.append("Authorization", `JWT ${accessToken}`);
const request = new Request(endpoint, { headers });
const resp = await fetch(request);
const routes = await resp.json();
if ("routes" in routes) {
return momentizeTimes(routes.routes);
}
}
const accessToken = CommaAuth.getCommaAccessToken();
if(accessToken) {
const endpoint = ROUTES_ENDPOINT.replace('{dongleId}', dongleId);
const headers = new Headers();
headers.append('Authorization', `JWT ${accessToken}`);
const request = new Request(endpoint, {headers});
const resp = await fetch(request);
const routes = await resp.json();
if('routes' in routes) {
return momentizeTimes(routes.routes);
}
}
return {};
return {};
}
export function cameraPath(routeUrl, frame) {
return `${routeUrl}/sec${frame}.jpg`
return `${routeUrl}/sec${frame}.jpg`;
}
export function parseRouteName(name) {
const startTime = Moment(name, "YYYY-MM-DD--H-m-s");
return {startTime};
return { startTime };
}

View File

@ -1,12 +1,12 @@
import SocketIO from 'socket.io-client';
import {UNLOGGER_HOST} from '../config';
import SocketIO from "socket.io-client";
import { UNLOGGER_HOST } from "../config";
export default class UnloggerClient {
constructor() {
this.socket = SocketIO(UNLOGGER_HOST);
}
constructor() {
this.socket = SocketIO(UNLOGGER_HOST);
}
seek(dongleId, baseTime, seekSeconds) {
this.socket.emit('seek', dongleId, baseTime, seekSeconds);
}
}
seek(dongleId, baseTime, seekSeconds) {
this.socket.emit("seek", dongleId, baseTime, seekSeconds);
}
}

View File

@ -5,16 +5,18 @@ function videoUrl(dongleId, hashedRouteName) {
function videoUrlForRouteUrl(routeUrlString) {
const url = new URL(routeUrlString);
const pathParts = url.pathname.split('/');
const pathParts = url.pathname.split("/");
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(pathParts.length - 2);
let dongleId = dongleIdPrefixed
if(dongleIdPrefixed.indexOf('comma-') === 0) {
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split('comma-');
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(
pathParts.length - 2
);
let dongleId = dongleIdPrefixed;
if (dongleIdPrefixed.indexOf("comma-") === 0) {
const [_, dongleIdNoPrefix] = dongleIdPrefixed.split("comma-");
dongleId = dongleIdNoPrefix;
}
return videoUrl(dongleId, hashedRouteName);
}
export default {videoUrl, videoUrlForRouteUrl};
export default { videoUrl, videoUrlForRouteUrl };

View File

@ -1,4 +1,4 @@
import DBC from './models/can/dbc';
import DBC from "./models/can/dbc";
const CivicDbc = new DBC(`
VERSION ""
@ -345,4 +345,4 @@ VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ;
VAL_ 927 ACC_ALERTS 29 "esp_active_acc_canceled" 10 "b_pedal_applied" 9 "speed_too_low" 8 "speed_too_high" 7 "p_brake_applied" 6 "gear_no_d" 5 "seatbelt" 4 "too_steep_downhill" 3 "too_steep_uphill" 2 "too_close" 1 "no_vehicle_ahead" ;
`);
export default CivicDbc;
export default CivicDbc;

File diff suppressed because it is too large Load Diff

View File

@ -1,268 +1,306 @@
import React, {Component} from 'react';
import Measure from 'react-measure';
import PropTypes from 'prop-types';
import cx from 'classnames';
import React, { Component } from "react";
import Measure from "react-measure";
import PropTypes from "prop-types";
import cx from "classnames";
import Signal from '../models/can/signal';
import CanPlot from '../vega/CanPlot';
import Signal from "../models/can/signal";
import CanPlot from "../vega/CanPlot";
const DefaultPlotInnerStyle = {
position: 'absolute',
top: 0,
left: 0,
position: "absolute",
top: 0,
left: 0
};
export default class CanGraph extends Component {
static emptyTable = [];
static emptyTable = [];
static propTypes = {
data: PropTypes.object,
messages: PropTypes.object,
messageId: PropTypes.string,
messageName: PropTypes.string,
signalSpec: PropTypes.instanceOf(Signal),
segment: PropTypes.array,
unplot: PropTypes.func,
onRelativeTimeClick: PropTypes.func,
currentTime: PropTypes.number,
onSegmentChanged: PropTypes.func,
onDragStart: PropTypes.func,
onDragEnd: PropTypes.func,
container: PropTypes.node,
dragPos: PropTypes.object,
canReceiveGraphDrop: PropTypes.bool,
onGraphRefAvailable: PropTypes.func,
plottedSignals: PropTypes.array,
static propTypes = {
data: PropTypes.object,
messages: PropTypes.object,
messageId: PropTypes.string,
messageName: PropTypes.string,
signalSpec: PropTypes.instanceOf(Signal),
segment: PropTypes.array,
unplot: PropTypes.func,
onRelativeTimeClick: PropTypes.func,
currentTime: PropTypes.number,
onSegmentChanged: PropTypes.func,
onDragStart: PropTypes.func,
onDragEnd: PropTypes.func,
container: PropTypes.node,
dragPos: PropTypes.object,
canReceiveGraphDrop: PropTypes.bool,
onGraphRefAvailable: PropTypes.func,
plottedSignals: PropTypes.array
};
constructor(props) {
super(props);
this.state = {
plotInnerStyle: null,
shiftX: 0,
shiftY: 0,
bounds: null,
isDataInserted: false
};
this.onNewView = this.onNewView.bind(this);
this.onSignalClickTime = this.onSignalClickTime.bind(this);
this.onSignalSegment = this.onSignalSegment.bind(this);
this.onDragAnchorMouseDown = this.onDragAnchorMouseDown.bind(this);
this.onDragAnchorMouseUp = this.onDragAnchorMouseUp.bind(this);
this.onDragStart = this.onDragStart.bind(this);
this.onPlotResize = this.onPlotResize.bind(this);
}
constructor(props) {
super(props);
segmentIsNew(newSegment) {
return (
newSegment.length !== this.props.segment.length ||
!newSegment.every((val, idx) => this.props.segment[idx] === val)
);
}
this.state = {
plotInnerStyle: null,
shiftX: 0,
shiftY: 0,
bounds: null,
isDataInserted: false,
};
this.onNewView = this.onNewView.bind(this);
this.onSignalClickTime = this.onSignalClickTime.bind(this);
this.onSignalSegment = this.onSignalSegment.bind(this);
this.onDragAnchorMouseDown = this.onDragAnchorMouseDown.bind(this);
this.onDragAnchorMouseUp = this.onDragAnchorMouseUp.bind(this);
this.onDragStart = this.onDragStart.bind(this);
this.onPlotResize = this.onPlotResize.bind(this);
}
dataChanged(prevProps, nextProps) {
return (
nextProps.data.series.length !== prevProps.data.series.length ||
!prevProps.signalSpec.equals(nextProps.signalSpec) ||
nextProps.data.updated !== this.props.data.updated
);
}
segmentIsNew(newSegment) {
return newSegment.length !== this.props.segment.length
|| !(newSegment.every((val, idx) => this.props.segment[idx] === val));
}
visualChanged(prevProps, nextProps) {
return (
prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop ||
JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos)
);
}
dataChanged(prevProps, nextProps) {
return nextProps.data.series.length !== prevProps.data.series.length
|| !prevProps.signalSpec.equals(nextProps.signalSpec)
|| nextProps.data.updated !== this.props.data.updated;
}
onPlotResize({ bounds }) {
this.setState({ bounds });
visualChanged(prevProps, nextProps) {
return prevProps.canReceiveGraphDrop !== nextProps.canReceiveGraphDrop
|| JSON.stringify(prevProps.dragPos) !== JSON.stringify(nextProps.dragPos);
}
this.view.run();
this.view.signal("width", bounds.width);
this.view.signal("height", 0.4 * bounds.width); // 5:2 aspect ratio
this.view.run();
}
shouldComponentUpdate(nextProps, nextState) {
if (this.view) {
// only update if segment is new
let segmentChanged = false;
if (this.segmentIsNew(nextProps.segment)) {
if (nextProps.segment.length > 0) {
// Set segmented domain
this.view.signal("segment", nextProps.segment);
} else {
// Reset segment to full domain
this.view.signal("segment", 0);
}
segmentChanged = true;
}
onPlotResize({bounds}) {
this.setState({bounds});
if (!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
this.view.signal("videoTime", nextProps.currentTime);
segmentChanged = true;
}
if (segmentChanged) {
this.view.run();
this.view.signal('width', bounds.width);
this.view.signal('height', 0.4 * bounds.width); // 5:2 aspect ratio
this.view.run();
}
}
shouldComponentUpdate(nextProps, nextState) {
if(this.view) {
// only update if segment is new
let segmentChanged = false;
if(this.segmentIsNew(nextProps.segment)) {
if(nextProps.segment.length > 0) {
// Set segmented domain
this.view.signal('segment', nextProps.segment)
} else {
// Reset segment to full domain
this.view.signal('segment', 0);
}
segmentChanged = true;
}
const dataChanged = this.dataChanged(this.props, nextProps);
if(!nextProps.live && nextProps.currentTime !== this.props.currentTime) {
this.view.signal('videoTime', nextProps.currentTime);
segmentChanged = true;
}
return (
dataChanged ||
JSON.stringify(this.state) !== JSON.stringify(nextState) ||
this.visualChanged(this.props, nextProps)
);
}
if(segmentChanged) {
this.view.run();
}
}
insertData() {
this.view.remove("table", () => true).run();
this.view.insert("table", this.props.data.series).run();
}
const dataChanged = this.dataChanged(this.props, nextProps);
componentDidUpdate(prevProps, prevState) {
if (this.dataChanged(prevProps, this.props)) {
this.insertData();
}
}
return dataChanged
|| JSON.stringify(this.state) !== JSON.stringify(nextState)
|| this.visualChanged(this.props, nextProps);
componentWillReceiveProps(nextProps) {
if (
nextProps.dragPos &&
JSON.stringify(nextProps.dragPos) !== JSON.stringify(this.props.dragPos)
) {
this.updateStyleFromDragPos(nextProps.dragPos);
} else if (!nextProps.dragPos && this.state.plotInnerStyle !== null) {
this.setState({ plotInnerStyle: null });
}
}
updateStyleFromDragPos({ left, top }) {
const plotInnerStyle = this.state.plotInnerStyle || {};
plotInnerStyle.left = left;
plotInnerStyle.top = top;
this.setState({ plotInnerStyle });
}
onNewView(view) {
this.view = view;
if (this.state.bounds) {
this.onPlotResize({ bounds: this.state.bounds });
}
if (this.props.segment.length > 0) {
view.signal("segment", this.props.segment);
}
insertData() {
this.view.remove('table', () => true).run();
this.view.insert('table', this.props.data.series).run();
this.insertData();
}
onSignalClickTime(signal, clickTime) {
if (clickTime !== undefined) {
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
}
}
onSignalSegment(signal, segment) {
if (!Array.isArray(segment)) {
return;
}
componentDidUpdate(prevProps, prevState) {
if(this.dataChanged(prevProps, this.props)) {
this.insertData();
}
this.props.onSegmentChanged(this.props.messageId, segment);
if (this.view) {
const state = this.view.getState();
state.subcontext[0].signals.brush = 0;
this.view = this.view.setState(state);
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.dragPos && JSON.stringify(nextProps.dragPos) !== JSON.stringify(this.props.dragPos)) {
this.updateStyleFromDragPos(nextProps.dragPos);
} else if(!nextProps.dragPos && this.state.plotInnerStyle !== null) {
this.setState({plotInnerStyle: null});
}
}
plotInnerStyleFromMouseEvent(e) {
const { shiftX, shiftY } = this.state;
const plotInnerStyle = { ...DefaultPlotInnerStyle };
const rect = this.props.container.getBoundingClientRect();
updateStyleFromDragPos({left, top}) {
const plotInnerStyle = this.state.plotInnerStyle || {};
plotInnerStyle.left = left;
plotInnerStyle.top = top;
this.setState({plotInnerStyle});
}
const x = e.clientX - rect.left - shiftX;
const y = e.clientY - rect.top - shiftY;
plotInnerStyle.left = x;
plotInnerStyle.top = y;
return plotInnerStyle;
}
onNewView(view) {
this.view = view;
if(this.state.bounds) {
this.onPlotResize({bounds: this.state.bounds});
}
if(this.props.segment.length > 0) {
view.signal('segment', this.props.segment);
}
onDragAnchorMouseDown(e) {
e.persist();
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
this.setState({ shiftX, shiftY }, () => {
this.setState({ plotInnerStyle: this.plotInnerStyleFromMouseEvent(e) });
});
this.props.onDragStart(
this.props.messageId,
this.props.signalSpec.uid,
shiftX,
shiftY
);
}
this.insertData();
}
onDragAnchorMouseUp(e) {
this.props.onDragEnd();
this.setState({
plotInnerStyle: null,
shiftX: 0,
shiftY: 0
});
}
onSignalClickTime(signal, clickTime) {
if(clickTime !== undefined) {
this.props.onRelativeTimeClick(this.props.messageId, clickTime);
}
}
onDragStart(e) {
e.preventDefault();
return false;
}
onSignalSegment(signal, segment) {
if(!Array.isArray(segment)) {
return;
}
render() {
const { plotInnerStyle } = this.state;
const canReceiveDropClass = this.props.canReceiveGraphDrop
? "is-droppable"
: null;
this.props.onSegmentChanged(this.props.messageId, segment);
if(this.view) {
const state = this.view.getState();
state.subcontext[0].signals.brush = 0;
this.view = this.view.setState(state);
}
}
return (
<div
className="cabana-explorer-visuals-plot"
ref={this.props.onGraphRefAvailable}
>
<div
className={cx(
"cabana-explorer-visuals-plot-inner",
canReceiveDropClass
)}
style={plotInnerStyle || null}
>
<div
className="cabana-explorer-visuals-plot-draganchor"
onMouseDown={this.onDragAnchorMouseDown}
>
<span className="fa fa-bars" />
</div>
{this.props.plottedSignals.map(
({ messageId, signalUid, messageName }) => {
const signal = Object.values(
this.props.messages[messageId].frame.signals
).find(s => s.uid === signalUid);
const { colors } = signal;
plotInnerStyleFromMouseEvent(e) {
const {shiftX, shiftY} = this.state;
const plotInnerStyle = {...DefaultPlotInnerStyle};
const rect = this.props.container.getBoundingClientRect();
const x = e.clientX - rect.left - shiftX;
const y = e.clientY - rect.top - shiftY;
plotInnerStyle.left = x;
plotInnerStyle.top = y;
return plotInnerStyle;
}
onDragAnchorMouseDown(e) {
e.persist();
const shiftX = e.clientX - e.target.getBoundingClientRect().left;
const shiftY = e.clientY - e.target.getBoundingClientRect().top;
this.setState({shiftX, shiftY},
() => {
this.setState({plotInnerStyle: this.plotInnerStyleFromMouseEvent(e)});
});
this.props.onDragStart(this.props.messageId, this.props.signalSpec.uid, shiftX, shiftY);
}
onDragAnchorMouseUp(e) {
this.props.onDragEnd();
this.setState({plotInnerStyle: null,
shiftX: 0,
shiftY: 0});
}
onDragStart(e) {
e.preventDefault();
return false;
}
render() {
const {plotInnerStyle} = this.state;
const canReceiveDropClass = this.props.canReceiveGraphDrop ? 'is-droppable' : null;
return (
<div className='cabana-explorer-visuals-plot'
ref={this.props.onGraphRefAvailable}>
<div className={ cx('cabana-explorer-visuals-plot-inner', canReceiveDropClass) }
style={ plotInnerStyle || null }>
<div className='cabana-explorer-visuals-plot-draganchor'
onMouseDown={this.onDragAnchorMouseDown}>
<span className='fa fa-bars'></span>
return (
<div
className="cabana-explorer-visuals-plot-header"
key={messageId + "_" + signal.uid}
>
<div className="cabana-explorer-visuals-plot-header-toggle">
<button
className="button--tiny"
onClick={() => this.props.unplot(messageId, signalUid)}
>
<span>Hide Plot</span>
</button>
</div>
<div className="cabana-explorer-visuals-plot-header-copy">
<div className="cabana-explorer-visuals-plot-message">
<span>{messageName}</span>
</div>
{this.props.plottedSignals.map(({ messageId, signalUid, messageName }) => {
const signal = Object.values(this.props.messages[messageId].frame.signals).find((s) => s.uid === signalUid);
const { colors } = signal;
return (
<div className='cabana-explorer-visuals-plot-header'
key={messageId + '_' + signal.uid}>
<div className='cabana-explorer-visuals-plot-header-toggle'>
<button className='button--tiny'
onClick={ () => this.props.unplot(messageId, signalUid)}>
<span>Hide Plot</span>
</button>
</div>
<div className='cabana-explorer-visuals-plot-header-copy'>
<div className='cabana-explorer-visuals-plot-message'>
<span>{messageName}</span>
</div>
<div className='cabana-explorer-visuals-plot-signal'>
<div className='cabana-explorer-visuals-plot-signal-color'
style={{background: `rgb(${colors}`}}></div>
<strong>{signal.name}</strong>
</div>
</div>
</div>
)}
)}
<Measure
bounds
onResize={this.onPlotResize}>
{({measureRef}) => {
return (<div ref={measureRef}
className='cabana-explorer-visuals-plot-container'>
<CanPlot
logLevel={0}
data={{table: CanGraph.emptyTable}}
onNewView={this.onNewView}
onSignalClickTime={this.onSignalClickTime}
onSignalSegment={this.onSignalSegment}
renderer={'canvas'}
/>
</div>);
}
}
</Measure>
<div className="cabana-explorer-visuals-plot-signal">
<div
className="cabana-explorer-visuals-plot-signal-color"
style={{ background: `rgb(${colors}` }}
/>
<strong>{signal.name}</strong>
</div>
</div>
</div>
</div>
);
}
);
}
)}
<Measure bounds onResize={this.onPlotResize}>
{({ measureRef }) => {
return (
<div
ref={measureRef}
className="cabana-explorer-visuals-plot-container"
>
<CanPlot
logLevel={0}
data={{ table: CanGraph.emptyTable }}
onNewView={this.onNewView}
onSignalClickTime={this.onSignalClickTime}
onSignalSegment={this.onSignalSegment}
renderer={"canvas"}
/>
</div>
);
}}
</Measure>
</div>
</div>
);
}
}

View File

@ -1,268 +1,310 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactList from 'react-list';
import React, { Component } from "react";
import PropTypes from "prop-types";
import ReactList from "react-list";
import cx from 'classnames';
import cx from "classnames";
export default class CanLog extends Component {
static ITEMS_PER_PAGE = 50;
static ITEMS_PER_PAGE = 50;
static propTypes = {
plottedSignals: PropTypes.array,
segmentIndices: PropTypes.array,
onSignalUnplotPressed: PropTypes.func,
onSignalPlotPressed: PropTypes.func,
message: PropTypes.object,
messageIndex: PropTypes.number,
onMessageExpanded: PropTypes.func,
static propTypes = {
plottedSignals: PropTypes.array,
segmentIndices: PropTypes.array,
onSignalUnplotPressed: PropTypes.func,
onSignalPlotPressed: PropTypes.func,
message: PropTypes.object,
messageIndex: PropTypes.number,
onMessageExpanded: PropTypes.func
};
constructor(props) {
super(props);
// only want to display up to length elements at a time
// offset, length
this.state = {
length: 0,
expandedMessages: [],
messageHeights: [],
allPacketsExpanded: false
};
constructor(props) {
super(props);
// only want to display up to length elements at a time
// offset, length
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
this.renderLogListItem = this.renderLogListItem.bind(this);
this.renderLogList = this.renderLogList.bind(this);
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
}
this.state = {
length: 0,
expandedMessages: [],
messageHeights: [],
allPacketsExpanded: false
}
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
this.renderLogListItem = this.renderLogListItem.bind(this);
this.renderLogList = this.renderLogList.bind(this);
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
componentWillReceiveProps(nextProps) {
if (nextProps.message && !this.props.message) {
this.addDisplayedMessages();
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.message && !this.props.message) {
this.addDisplayedMessages();
}
}
shouldComponentUpdate(nextProps, nextState) {
const curMessageLength = this.props.message
? this.props.message.entries.length
: 0;
const nextMessageLength = nextProps.message
? nextProps.message.entries.length
: 0;
shouldComponentUpdate(nextProps, nextState) {
const curMessageLength = this.props.message ? this.props.message.entries.length : 0;
const nextMessageLength = nextProps.message ? nextProps.message.entries.length : 0;
const shouldUpdate =
this.props.message !== nextProps.message ||
nextMessageLength !== curMessageLength ||
nextProps.messageIndex !== this.props.messageIndex ||
nextProps.plottedSignals.length !== this.props.plottedSignals.length ||
JSON.stringify(nextProps.segmentIndices) !==
JSON.stringify(this.props.segmentIndices) ||
JSON.stringify(nextState) !== JSON.stringify(this.state) ||
this.props.message !== nextProps.message ||
(this.props.message !== undefined &&
nextProps.message !== undefined &&
this.props.message.frame !== undefined &&
nextProps.message.frame !== undefined &&
JSON.stringify(this.props.message.frame) !==
JSON.stringify(nextProps.message.frame));
const shouldUpdate = this.props.message !== nextProps.message
|| nextMessageLength !== curMessageLength
|| nextProps.messageIndex !== this.props.messageIndex
|| nextProps.plottedSignals.length !== this.props.plottedSignals.length
|| JSON.stringify(nextProps.segmentIndices) !== JSON.stringify(this.props.segmentIndices)
|| JSON.stringify(nextState) !== JSON.stringify(this.state)
|| this.props.message !== nextProps.message
|| (this.props.message !== undefined
&& nextProps.message !== undefined
&& this.props.message.frame !== undefined
&& nextProps.message.frame !== undefined
&&
(
(JSON.stringify(this.props.message.frame) !== JSON.stringify(nextProps.message.frame))
));
return shouldUpdate;
}
return shouldUpdate;
}
addDisplayedMessages() {
const { length } = this.state;
const newLength = length + CanLog.ITEMS_PER_PAGE;
addDisplayedMessages() {
const {length} = this.state;
const newLength = length + CanLog.ITEMS_PER_PAGE;
this.setState({ length: newLength });
}
this.setState({length: newLength});
}
expandMessage(msg, msgIdx) {
this.setState({
expandedMessages: this.state.expandedMessages.concat([msg.time])
});
this.props.onMessageExpanded();
}
expandMessage(msg, msgIdx) {
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
this.props.onMessageExpanded();
}
collapseMessage(msg, msgIdx) {
this.setState({expandedMessages: this.state.expandedMessages
.filter((expMsgTime) => expMsgTime !== msg.time)})
}
isSignalPlotted(msgId, signalUid) {
const plottedSignal = this.props.plottedSignals.find((plot) =>
plot.some((signal) => signal.messageId === msgId && signal.signalUid === signalUid));
return plottedSignal !== undefined;
}
signalValuePretty(signal, value) {
if(signal.isFloat) {
return value.toFixed(3);
} else return value;
}
isMessageExpanded(msg) {
return this.state.expandedMessages.indexOf(msg.time) !== -1;
}
toggleSignalPlot(msg, signalUid, plotted) {
if (!plotted) {
this.props.onSignalPlotPressed(msg, signalUid);
} else {
this.props.onSignalUnplotPressed(msg, signalUid);
}
}
toggleExpandPacketSignals(msgEntry) {
if(!this.props.message.frame) {
return;
}
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
const msgHasSignals = Object.keys(this.props.message.frame.signals).length > 0;
if (msgIsExpanded && msgHasSignals) {
this.setState({expandedMessages: this.state.expandedMessages
.filter((expMsgTime) => expMsgTime !== msgEntry.time)})
} else if (msgHasSignals) {
this.setState({expandedMessages: this.state.expandedMessages.concat([msgEntry.time])})
this.props.onMessageExpanded();
} else { return; }
}
renderLogListItemSignals(msgEntry) {
const { message } = this.props;
return (
<div className='signals-log-list-signals'>
{ Object.entries(msgEntry.signals).map(
([name, value]) => {
const signal = message.frame.signals[name];
if (signal === undefined) {
// Signal removed?
return null;
}
const unit = signal.unit.length > 0 ? signal.unit : 'units';
const isPlotted = this.isSignalPlotted(message.id, signal.uid);
const plottedButtonClass = isPlotted ? null : 'button--alpha';
const plottedButtonText = isPlotted ? 'Hide Plot' : 'Show Plot';
return (
<div key={ name } className='signals-log-list-signal'>
<div className='signals-log-list-signal-message'>
<span>{ name }</span>
</div>
<div className='signals-log-list-signal-value'>
<span>
(<strong>{ this.signalValuePretty(signal, value) }</strong> { unit })
</span>
</div>
<div className='signals-log-list-signal-action'
onClick={ () => { this.toggleSignalPlot(message.id, signal.uid, isPlotted) } }>
<button className={ cx('button--tiny', plottedButtonClass) }>
<span>{ plottedButtonText }</span>
</button>
</div>
</div>
);
})}
</div>
collapseMessage(msg, msgIdx) {
this.setState({
expandedMessages: this.state.expandedMessages.filter(
expMsgTime => expMsgTime !== msg.time
)
});
}
isSignalPlotted(msgId, signalUid) {
const plottedSignal = this.props.plottedSignals.find(plot =>
plot.some(
signal => signal.messageId === msgId && signal.signalUid === signalUid
)
);
return plottedSignal !== undefined;
}
signalValuePretty(signal, value) {
if (signal.isFloat) {
return value.toFixed(3);
} else return value;
}
isMessageExpanded(msg) {
return this.state.expandedMessages.indexOf(msg.time) !== -1;
}
toggleSignalPlot(msg, signalUid, plotted) {
if (!plotted) {
this.props.onSignalPlotPressed(msg, signalUid);
} else {
this.props.onSignalUnplotPressed(msg, signalUid);
}
}
renderLogListItemMessage(msgEntry, key) {
const { message } = this.props;
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
const msgHasSignals = Object.keys(msgEntry.signals).length > 0;
const hasSignalsClass = msgHasSignals ? 'has-signals' : null;
const expandedClass = msgIsExpanded ? 'is-expanded' : null;
const row = (
<div key={key} className={cx('signals-log-list-item', hasSignalsClass, expandedClass)}>
<div className='signals-log-list-item-header'
onClick={ () => { this.toggleExpandPacketSignals(msgEntry) } }>
<div className='signals-log-list-message'>
<strong>{(message.frame ? message.frame.name : null) || message.id}</strong>
</div>
<div className='signals-log-list-time'>
<span>[{msgEntry.relTime.toFixed(3)}]</span>
</div>
<div className='signals-log-list-bytes'>
<span className='t-mono'>{msgEntry.hexData}</span>
</div>
</div>
<div className='signals-log-list-item-body'>
{ msgIsExpanded ? this.renderLogListItemSignals(msgEntry) : null}
</div>
</div>
);
return row;
toggleExpandPacketSignals(msgEntry) {
if (!this.props.message.frame) {
return;
}
const msgIsExpanded =
this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
renderLogListItem(index, key) {
let offset = this.props.messageIndex;
if(offset === 0 && this.props.segmentIndices.length === 2) {
offset = this.props.segmentIndices[0];
}
if((offset + index) < this.props.message.entries.length) {
return this.renderLogListItemMessage(this.props.message.entries[offset + index], key);
} else {
return null;
}
}
renderLogList(items, ref) {
return (
<div className='signals-log-list'>
<div className='signals-log-list-header'>
<div className='signals-log-list-message'>Message</div>
<div className='signals-log-list-time'>Time</div>
<div className='signals-log-list-bytes'>Bytes</div>
</div>
<div className='signals-log-list-items'
ref={ref}>
{items}
</div>
</div>
const msgHasSignals =
Object.keys(this.props.message.frame.signals).length > 0;
if (msgIsExpanded && msgHasSignals) {
this.setState({
expandedMessages: this.state.expandedMessages.filter(
expMsgTime => expMsgTime !== msgEntry.time
)
});
} else if (msgHasSignals) {
this.setState({
expandedMessages: this.state.expandedMessages.concat([msgEntry.time])
});
this.props.onMessageExpanded();
} else {
return;
}
}
listLength() {
const {segmentIndices, messageIndex} = this.props;
if(messageIndex > 0) {
return this.props.message.entries.length - messageIndex;
} else if(segmentIndices.length === 2) {
return segmentIndices[1] - segmentIndices[0];
} else if(this.props.message) {
return this.props.message.entries.length;
} else {
// no message yet
return 0;
}
}
onExpandAllChanged(e) {
this.setState({allPacketsExpanded: e.target.checked});
}
toggleExpandAllPackets() {
this.setState({allPacketsExpanded: !this.state.allPacketsExpanded});
}
render() {
let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All';
let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha';
return (
<div className='cabana-explorer-signals-log'>
<div className='cabana-explorer-signals-log-header'>
<strong>Message Packets</strong>
<button className={cx('button--tiny', expandAllClass)}
onClick={this.toggleExpandAllPackets}>
{expandAllText}
</button>
</div>
<div className='cabana-explorer-signals-log-body'>
<ReactList
itemRenderer={this.renderLogListItem}
itemsRenderer={this.renderLogList}
length={this.listLength()}
pageSize={50}
updateWhenThisValueChanges={this.props.messageIndex}
type='variable' />
renderLogListItemSignals(msgEntry) {
const { message } = this.props;
return (
<div className="signals-log-list-signals">
{Object.entries(msgEntry.signals).map(([name, value]) => {
const signal = message.frame.signals[name];
if (signal === undefined) {
// Signal removed?
return null;
}
const unit = signal.unit.length > 0 ? signal.unit : "units";
const isPlotted = this.isSignalPlotted(message.id, signal.uid);
const plottedButtonClass = isPlotted ? null : "button--alpha";
const plottedButtonText = isPlotted ? "Hide Plot" : "Show Plot";
return (
<div key={name} className="signals-log-list-signal">
<div className="signals-log-list-signal-message">
<span>{name}</span>
</div>
<div className="signals-log-list-signal-value">
<span>
(<strong>{this.signalValuePretty(signal, value)}</strong>{" "}
{unit})
</span>
</div>
<div
className="signals-log-list-signal-action"
onClick={() => {
this.toggleSignalPlot(message.id, signal.uid, isPlotted);
}}
>
<button className={cx("button--tiny", plottedButtonClass)}>
<span>{plottedButtonText}</span>
</button>
</div>
</div>
);
})}
</div>
);
}
renderLogListItemMessage(msgEntry, key) {
const { message } = this.props;
const msgIsExpanded =
this.state.allPacketsExpanded || this.isMessageExpanded(msgEntry);
const msgHasSignals = Object.keys(msgEntry.signals).length > 0;
const hasSignalsClass = msgHasSignals ? "has-signals" : null;
const expandedClass = msgIsExpanded ? "is-expanded" : null;
const row = (
<div
key={key}
className={cx("signals-log-list-item", hasSignalsClass, expandedClass)}
>
<div
className="signals-log-list-item-header"
onClick={() => {
this.toggleExpandPacketSignals(msgEntry);
}}
>
<div className="signals-log-list-message">
<strong>
{(message.frame ? message.frame.name : null) || message.id}
</strong>
</div>
);
<div className="signals-log-list-time">
<span>[{msgEntry.relTime.toFixed(3)}]</span>
</div>
<div className="signals-log-list-bytes">
<span className="t-mono">{msgEntry.hexData}</span>
</div>
</div>
<div className="signals-log-list-item-body">
{msgIsExpanded ? this.renderLogListItemSignals(msgEntry) : null}
</div>
</div>
);
return row;
}
renderLogListItem(index, key) {
let offset = this.props.messageIndex;
if (offset === 0 && this.props.segmentIndices.length === 2) {
offset = this.props.segmentIndices[0];
}
if (offset + index < this.props.message.entries.length) {
return this.renderLogListItemMessage(
this.props.message.entries[offset + index],
key
);
} else {
return null;
}
}
renderLogList(items, ref) {
return (
<div className="signals-log-list">
<div className="signals-log-list-header">
<div className="signals-log-list-message">Message</div>
<div className="signals-log-list-time">Time</div>
<div className="signals-log-list-bytes">Bytes</div>
</div>
<div className="signals-log-list-items" ref={ref}>
{items}
</div>
</div>
);
}
listLength() {
const { segmentIndices, messageIndex } = this.props;
if (messageIndex > 0) {
return this.props.message.entries.length - messageIndex;
} else if (segmentIndices.length === 2) {
return segmentIndices[1] - segmentIndices[0];
} else if (this.props.message) {
return this.props.message.entries.length;
} else {
// no message yet
return 0;
}
}
onExpandAllChanged(e) {
this.setState({ allPacketsExpanded: e.target.checked });
}
toggleExpandAllPackets() {
this.setState({ allPacketsExpanded: !this.state.allPacketsExpanded });
}
render() {
let expandAllText = this.state.allPacketsExpanded
? "Collapse All"
: "Expand All";
let expandAllClass = this.state.allPacketsExpanded ? null : "button--alpha";
return (
<div className="cabana-explorer-signals-log">
<div className="cabana-explorer-signals-log-header">
<strong>Message Packets</strong>
<button
className={cx("button--tiny", expandAllClass)}
onClick={this.toggleExpandAllPackets}
>
{expandAllText}
</button>
</div>
<div className="cabana-explorer-signals-log-body">
<ReactList
itemRenderer={this.renderLogListItem}
itemsRenderer={this.renderLogList}
length={this.listLength()}
pageSize={50}
updateWhenThisValueChanges={this.props.messageIndex}
type="variable"
/>
</div>
</div>
);
}
}

View File

@ -1,40 +1,42 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class DbcUpload extends Component {
static propTypes = {
onDbcLoaded: PropTypes.func
static propTypes = {
onDbcLoaded: PropTypes.func
};
constructor(props) {
super(props);
this.state = {
dbcText: ""
};
constructor(props) {
super(props);
this.state = {
dbcText: ''
};
this.onTextChanged = this.onTextChanged.bind(this);
}
this.onTextChanged = this.onTextChanged.bind(this);
}
onTextChanged(e) {
const dbcText = e.target.value;
this.setState({dbcText})
this.props.onDbcLoaded('from paste', dbcText);
}
onTextChanged(e) {
const dbcText = e.target.value;
this.setState({ dbcText });
this.props.onDbcLoaded("from paste", dbcText);
}
render() {
return (
<div className='cabana-dbc-upload-raw'>
<div className='form-field'>
<label htmlFor='raw_dbc_upload'>
<span>Raw DBC File:</span>
<sup>Paste your DBC text output within this box</sup>
</label>
<textarea value={ this.state.dbcText }
id='raw_dbc_upload'
className='t-mono'
placeholder='PASTE DBC FILE HERE'
onChange={ this.onTextChanged } />
</div>
</div>
);
}
render() {
return (
<div className="cabana-dbc-upload-raw">
<div className="form-field">
<label htmlFor="raw_dbc_upload">
<span>Raw DBC File:</span>
<sup>Paste your DBC text output within this box</sup>
</label>
<textarea
value={this.state.dbcText}
id="raw_dbc_upload"
className="t-mono"
placeholder="PASTE DBC FILE HERE"
onChange={this.onTextChanged}
/>
</div>
</div>
);
}
}

View File

@ -1,123 +1,130 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import PropTypes from "prop-types";
import Modal from './Modals/baseModal';
import Modal from "./Modals/baseModal";
export default class EditMessageModal extends Component {
static propTypes = {
handleClose: PropTypes.func.isRequired,
handleSave: PropTypes.func.isRequired,
message: PropTypes.object.isRequired,
static propTypes = {
handleClose: PropTypes.func.isRequired,
handleSave: PropTypes.func.isRequired,
message: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.state = {
messageFrame: props.message.frame.copy()
};
this.handleSave = this.handleSave.bind(this);
this.editTransmitter = this.editTransmitter.bind(this);
this.addTransmitter = this.addTransmitter.bind(this);
this.renderActions = this.renderActions.bind(this);
}
constructor(props) {
super(props);
handleSave() {
this.props.handleSave(this.state.messageFrame);
}
this.state = {
messageFrame: props.message.frame.copy()
}
this.handleSave = this.handleSave.bind(this);
this.editTransmitter = this.editTransmitter.bind(this);
this.addTransmitter = this.addTransmitter.bind(this);
this.renderActions = this.renderActions.bind(this);
}
addTransmitter() {
const { messageFrame } = this.state;
messageFrame.addTransmitter();
this.setState({ messageFrame });
}
handleSave() {
this.props.handleSave(this.state.messageFrame);
}
editTransmitter(transmitter) {
return;
}
addTransmitter() {
const {messageFrame} = this.state;
messageFrame.addTransmitter();
this.setState({messageFrame});
}
renderActions() {
return (
<div>
<button className="button--inverted" onClick={this.props.handleClose}>
Cancel
</button>
<button className="button--primary" onClick={this.handleSave}>
Save Message
</button>
</div>
);
}
editTransmitter(transmitter) {
return;
}
renderActions() {
return (
<div>
<button
className='button--inverted'
onClick={ this.props.handleClose }>Cancel</button>
<button
className='button--primary'
onClick={ this.handleSave }>Save Message</button>
render() {
return (
<Modal
title={`Edit Message: (${this.props.message.id})`}
subtitle="Make changes and update defaults of this message"
handleClose={this.props.handleClose}
handleSave={this.handleSave}
actions={this.renderActions()}
>
<div className="form-field">
<label htmlFor="message_name">
<span>Name</span>
<sup>Customize the name of this message</sup>
</label>
<input
type="text"
id="message_name"
value={this.state.messageFrame.name}
onChange={e => {
const { messageFrame } = this.state;
messageFrame.name = e.target.value;
this.setState({ messageFrame });
}}
/>
</div>
)
}
render() {
return (
<Modal
title={ `Edit Message: (${ this.props.message.id })`}
subtitle='Make changes and update defaults of this message'
handleClose={ this.props.handleClose }
handleSave={ this.handleSave }
actions={ this.renderActions() }>
<div className='form-field'>
<label htmlFor='message_name'>
<span>Name</span>
<sup>Customize the name of this message</sup>
</label>
<input
type='text'
id='message_name'
value={ this.state.messageFrame.name }
onChange={ (e) => {
const { messageFrame } = this.state;
messageFrame.name = e.target.value;
this.setState({ messageFrame });
}} />
</div>
<div className='form-field'>
<label htmlFor='message_size'>
<span>Size</span>
<sup>Add a size parameter to this message</sup>
</label>
<input
type='number'
id='message_size'
value={this.state.messageFrame.size}
onChange={(e) => {
const {messageFrame} = this.state;
if(e.target.value > 8) {
return;
}
messageFrame.size = parseInt(e.target.value, 10);
this.setState({ messageFrame });
}} />
</div>
<div className='form-field u-hidden'>
<label htmlFor='message_transmitters'>
<span>Transmitters</span>
<sup>Add the physical ECU units that this message is coming from.</sup>
</label>
<div className='form-field-inset'>
<ul className='form-field-inset-list'>
{ this.state.messageFrame.transmitters.map((transmitter) => {
return (
<li className='form-field-inset-list-item' key={ transmitter }>
<div className='form-field-inset-list-item-title'>
<span>{ transmitter }</span>
</div>
<div className='form-field-inset-list-item-action'>
<button className='button--tiny button--alpha'>
Edit
</button>
</div>
</li>
);
}) }
<button className='button--tiny button--alpha'>
<span><i className='fa fa-plus'></i> Add Transmitter</span>
</button>
</ul>
<div className="form-field">
<label htmlFor="message_size">
<span>Size</span>
<sup>Add a size parameter to this message</sup>
</label>
<input
type="number"
id="message_size"
value={this.state.messageFrame.size}
onChange={e => {
const { messageFrame } = this.state;
if (e.target.value > 8) {
return;
}
messageFrame.size = parseInt(e.target.value, 10);
this.setState({ messageFrame });
}}
/>
</div>
<div className="form-field u-hidden">
<label htmlFor="message_transmitters">
<span>Transmitters</span>
<sup>
Add the physical ECU units that this message is coming from.
</sup>
</label>
<div className="form-field-inset">
<ul className="form-field-inset-list">
{this.state.messageFrame.transmitters.map(transmitter => {
return (
<li className="form-field-inset-list-item" key={transmitter}>
<div className="form-field-inset-list-item-title">
<span>{transmitter}</span>
</div>
</div>
</Modal>
);
}
<div className="form-field-inset-list-item-action">
<button className="button--tiny button--alpha">
Edit
</button>
</div>
</li>
);
})}
<button className="button--tiny button--alpha">
<span>
<i className="fa fa-plus" /> Add Transmitter
</span>
</button>
</ul>
</div>
</div>
</Modal>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +1,97 @@
import React, {Component} from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import OpenDbc from '../api/OpenDbc';
import OpenDbc from "../api/OpenDbc";
export default class GithubDbcList extends Component {
static propTypes = {
onDbcLoaded: PropTypes.func.isRequired,
repo: PropTypes.string.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
static propTypes = {
onDbcLoaded: PropTypes.func.isRequired,
repo: PropTypes.string.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired
};
constructor(props) {
super(props);
this.state = {
paths: [],
selectedPath: null,
pathQuery: ""
};
constructor(props){
super(props);
this.updatePathQuery = this.updatePathQuery.bind(this);
}
this.state = {
paths: [],
selectedPath: null,
pathQuery: ''
};
this.updatePathQuery = this.updatePathQuery.bind(this);
componentWillReceiveProps(nextProps) {
if (nextProps.repo !== this.props.repo) {
this.props.openDbcClient.list(nextProps.repo).then(paths => {
this.setState({ paths, selectedPath: null });
});
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.repo !== this.props.repo) {
this.props.openDbcClient.list(nextProps.repo).then((paths) => {
this.setState({paths, selectedPath: null})
})
}
}
componentWillMount() {
this.props.openDbcClient.list(this.props.repo).then(paths => {
paths = paths.filter(path => path.indexOf(".dbc") !== -1);
this.setState({ paths });
});
}
componentWillMount() {
this.props.openDbcClient.list(this.props.repo).then((paths) => {
paths = paths.filter((path) => path.indexOf(".dbc") !== -1);
this.setState({paths});
})
}
updatePathQuery(e) {
this.setState({
pathQuery: e.target.value
});
}
updatePathQuery(e) {
this.setState({
pathQuery: e.target.value,
})
}
selectPath(path) {
this.setState({ selectedPath: path });
this.props.openDbcClient
.getDbcContents(path, this.props.repo)
.then(dbcContents => {
this.props.onDbcLoaded(path, dbcContents);
});
}
selectPath(path) {
this.setState({selectedPath: path})
this.props.openDbcClient.getDbcContents(path, this.props.repo).then((dbcContents) => {
this.props.onDbcLoaded(path, dbcContents);
})
}
render() {
return (
<div className='cabana-dbc-list'>
<div className='cabana-dbc-list-header'>
<a href={ `https://github.com/${ this.props.repo }` }
target='_blank'>
<i className='fa fa-github'></i>
<span>{ this.props.repo }</span>
</a>
<div className='form-field form-field--small'>
<input type='text'
placeholder='Search DBC Files'
onChange={ this.updatePathQuery }/>
</div>
render() {
return (
<div className="cabana-dbc-list">
<div className="cabana-dbc-list-header">
<a href={`https://github.com/${this.props.repo}`} target="_blank">
<i className="fa fa-github" />
<span>{this.props.repo}</span>
</a>
<div className="form-field form-field--small">
<input
type="text"
placeholder="Search DBC Files"
onChange={this.updatePathQuery}
/>
</div>
</div>
<div className="cabana-dbc-list-files">
{this.state.paths
.filter(
p =>
(this.state.pathQuery === "") | p.includes(this.state.pathQuery)
)
.map(path => {
return (
<div
className={cx("cabana-dbc-list-file", {
"is-selected": this.state.selectedPath === path
})}
onClick={() => {
this.selectPath(path);
}}
key={path}
>
<span>{path}</span>
</div>
<div className='cabana-dbc-list-files'>
{ this.state.paths.filter(p => this.state.pathQuery === '' | p.includes(this.state.pathQuery))
.map((path) => {
return (
<div className={ cx('cabana-dbc-list-file', {'is-selected': this.state.selectedPath === path})}
onClick={ () => { this.selectPath(path) } }
key={path}>
<span>{ path }</span>
</div>
)
})}
</div>
</div>
);
}
);
})}
</div>
</div>
);
}
}

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Hls from 'hls.js/lib';
import React, { Component } from "react";
import PropTypes from "prop-types";
import Hls from "hls.js/lib";
export default class HLS extends Component {
static propTypes = {
@ -19,13 +19,16 @@ export default class HLS extends Component {
};
componentWillReceiveProps(nextProps) {
if( (nextProps.shouldRestart || nextProps.startTime !== this.props.startTime)
&& isFinite(nextProps.startTime)) {
if (
(nextProps.shouldRestart ||
nextProps.startTime !== this.props.startTime) &&
isFinite(nextProps.startTime)
) {
this.videoElement.currentTime = nextProps.startTime;
this.props.onRestart();
}
if(nextProps.playing) {
if (nextProps.playing) {
this.videoElement.play();
} else {
this.videoElement.pause();
@ -37,20 +40,20 @@ export default class HLS extends Component {
this.player.loadSource(this.props.source);
this.player.attachMedia(this.videoElement);
// these events fire when video is playing
this.videoElement.addEventListener('waiting', this.props.onLoadStart);
this.videoElement.addEventListener('playing', this.props.onLoadEnd);
this.videoElement.addEventListener("waiting", this.props.onLoadStart);
this.videoElement.addEventListener("playing", this.props.onLoadEnd);
// these events fire when video is paused & seeked
this.videoElement.addEventListener('seeking', () => {
if(!this.props.playing) {
this.videoElement.addEventListener("seeking", () => {
if (!this.props.playing) {
this.props.onLoadStart();
this.props.onPlaySeek(this.videoElement.currentTime);
}
});
let shouldInitVideoTime = true;
this.videoElement.addEventListener('seeked', () => {
if(!this.props.playing) {
if(shouldInitVideoTime) {
this.videoElement.addEventListener("seeked", () => {
if (!this.props.playing) {
if (shouldInitVideoTime) {
this.videoElement.currentTime = this.props.startTime;
shouldInitVideoTime = false;
}
@ -59,7 +62,7 @@ export default class HLS extends Component {
});
this.props.onVideoElementAvailable(this.videoElement);
if(this.props.playing) {
if (this.props.playing) {
this.videoElement.play();
}
}
@ -67,9 +70,14 @@ export default class HLS extends Component {
render() {
return (
<div
className='cabana-explorer-visuals-camera-wrapper'
onClick={this.props.onClick}>
<video ref={ (video) => { this.videoElement = video; } } />
className="cabana-explorer-visuals-camera-wrapper"
onClick={this.props.onClick}
>
<video
ref={video => {
this.videoElement = video;
}}
/>
</div>
);
}

View File

@ -1,127 +1,129 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import DBC from '../models/can/dbc';
import OpenDbc from '../api/OpenDbc';
import Modal from './Modals/baseModal';
import GithubDbcList from './GithubDbcList';
import DbcUpload from './DbcUpload';
import DBC from "../models/can/dbc";
import OpenDbc from "../api/OpenDbc";
import Modal from "./Modals/baseModal";
import GithubDbcList from "./GithubDbcList";
import DbcUpload from "./DbcUpload";
export default class LoadDbcModal extends Component {
static propTypes = {
handleClose: PropTypes.func.isRequired,
onDbcSelected: PropTypes.func.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
loginWithGithub: PropTypes.element.isRequired,
static propTypes = {
handleClose: PropTypes.func.isRequired,
onDbcSelected: PropTypes.func.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
loginWithGithub: PropTypes.element.isRequired
};
constructor(props) {
super(props);
this.state = {
tab: "OpenDBC",
tabs: ["OpenDBC", "GitHub", "Upload"],
dbc: null,
dbcSource: null,
userOpenDbcRepo: null
};
constructor(props) {
super(props);
this.state = {
tab: 'OpenDBC',
tabs: ['OpenDBC', 'GitHub', 'Upload'],
dbc: null,
dbcSource: null,
userOpenDbcRepo: null,
}
this.onDbcLoaded = this.onDbcLoaded.bind(this);
this.handleSave = this.handleSave.bind(this);
this.renderTabNavigation = this.renderTabNavigation.bind(this);
this.renderTabContent = this.renderTabContent.bind(this);
this.renderActions = this.renderActions.bind(this);
}
this.onDbcLoaded = this.onDbcLoaded.bind(this);
this.handleSave = this.handleSave.bind(this);
this.renderTabNavigation = this.renderTabNavigation.bind(this);
this.renderTabContent = this.renderTabContent.bind(this);
this.renderActions = this.renderActions.bind(this);
componentWillMount() {
this.props.openDbcClient.getUserOpenDbcFork().then(userOpenDbcRepo => {
this.setState({ userOpenDbcRepo });
});
}
onDbcLoaded(dbcSource, dbcText) {
const dbc = new DBC(dbcText);
this.setState({ dbcSource, dbc });
}
handleSave() {
const { dbc, dbcSource } = this.state;
if (dbc !== null) {
this.props.onDbcSelected(dbcSource, dbc);
}
}
componentWillMount() {
this.props.openDbcClient.getUserOpenDbcFork().then((userOpenDbcRepo) => {
this.setState({userOpenDbcRepo});
});
}
onDbcLoaded(dbcSource, dbcText) {
const dbc = new DBC(dbcText);
this.setState({dbcSource, dbc})
}
handleSave() {
const { dbc, dbcSource } = this.state;
if(dbc !== null) {
this.props.onDbcSelected(dbcSource, dbc);
}
}
renderTabNavigation() {
return (
<div className='cabana-tabs-navigation'>
{ this.state.tabs.map((tab) => {
return (
<a className={ cx({'is-active': this.state.tab === tab})}
onClick={ () => { this.setState({ tab }) }}
key={tab}>
<span>{ tab }</span>
</a>
)
})}
</div>
)
}
renderTabContent() {
const { tab } = this.state;
if (tab === 'OpenDBC') {
return (
<GithubDbcList
onDbcLoaded={ this.onDbcLoaded }
repo="commaai/opendbc"
openDbcClient={ this.props.openDbcClient } />
);
} else if (tab === 'GitHub') {
if (!this.props.openDbcClient.hasAuth()) {
return this.props.loginWithGithub;
} else if (this.state.userOpenDbcRepo === null) {
return (<div>Fork it</div>);
} else {
return (
<GithubDbcList
onDbcLoaded={this.onDbcLoaded}
repo={this.state.userOpenDbcRepo}
openDbcClient={this.props.openDbcClient} />
);
}
} else if (tab === 'Upload') {
renderTabNavigation() {
return (
<div className="cabana-tabs-navigation">
{this.state.tabs.map(tab => {
return (
<DbcUpload
onDbcLoaded={this.onDbcLoaded} />
<a
className={cx({ "is-active": this.state.tab === tab })}
onClick={() => {
this.setState({ tab });
}}
key={tab}
>
<span>{tab}</span>
</a>
);
}
}
})}
</div>
);
}
renderActions() {
renderTabContent() {
const { tab } = this.state;
if (tab === "OpenDBC") {
return (
<GithubDbcList
onDbcLoaded={this.onDbcLoaded}
repo="commaai/opendbc"
openDbcClient={this.props.openDbcClient}
/>
);
} else if (tab === "GitHub") {
if (!this.props.openDbcClient.hasAuth()) {
return this.props.loginWithGithub;
} else if (this.state.userOpenDbcRepo === null) {
return <div>Fork it</div>;
} else {
return (
<div>
<button className='button--inverted'
onClick={ this.props.handleClose }>
<span>Cancel</span>
</button>
<button className='button--primary'
onClick={ this.handleSave }>
<span>Load DBC</span>
</button>
</div>
<GithubDbcList
onDbcLoaded={this.onDbcLoaded}
repo={this.state.userOpenDbcRepo}
openDbcClient={this.props.openDbcClient}
/>
);
}
} else if (tab === "Upload") {
return <DbcUpload onDbcLoaded={this.onDbcLoaded} />;
}
}
render() {
return (
<Modal
title='Load DBC File'
subtitle='Modify an existing DBC file with Cabana'
handleClose={ this.props.handleClose }
navigation={ this.renderTabNavigation() }
actions={ this.renderActions() }>
{ this.renderTabContent() }
</Modal>
);
}
renderActions() {
return (
<div>
<button className="button--inverted" onClick={this.props.handleClose}>
<span>Cancel</span>
</button>
<button className="button--primary" onClick={this.handleSave}>
<span>Load DBC</span>
</button>
</div>
);
}
render() {
return (
<Modal
title="Load DBC File"
subtitle="Modify an existing DBC file with Cabana"
handleClose={this.props.handleClose}
navigation={this.renderTabNavigation()}
actions={this.renderActions()}
>
{this.renderTabContent()}
</Modal>
);
}
}

View File

@ -1,42 +1,42 @@
import React, { Component } from 'react';
import { css, StyleSheet } from 'aphrodite/no-important';
import React, { Component } from "react";
import { css, StyleSheet } from "aphrodite/no-important";
const keyframes = {
'0%': {
transform: 'translateX(0)'
},
to: {
transform: 'translateX(-400px)'
}
"0%": {
transform: "translateX(0)"
},
to: {
transform: "translateX(-400px)"
}
};
const animationColor1 = 'RGBA(74, 242, 161, 1.00)';
const animationColor2 = 'RGBA(140, 169, 197, 1.00)';
const animationColor1 = "RGBA(74, 242, 161, 1.00)";
const animationColor2 = "RGBA(140, 169, 197, 1.00)";
const Styles = StyleSheet.create({
loadingBar: {
display: 'block',
animationName: [keyframes],
animationDuration: '2s',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite',
backgroundColor: animationColor1,
backgroundImage: `linear-gradient(to right,
loadingBar: {
display: "block",
animationName: [keyframes],
animationDuration: "2s",
animationTimingFunction: "linear",
animationIterationCount: "infinite",
backgroundColor: animationColor1,
backgroundImage: `linear-gradient(to right,
${animationColor2} 0,
${animationColor2} 50%,
${animationColor1} 50%,
${animationColor1} 100%)`,
backgroundRepeat: 'repeat-x',
backgroundSize: '25pc 25pc',
width: '200%',
position: 'fixed',
top: 0,
left: 0,
height: 2
}
backgroundRepeat: "repeat-x",
backgroundSize: "25pc 25pc",
width: "200%",
position: "fixed",
top: 0,
left: 0,
height: 2
}
});
export default class LoadingBar extends Component {
render() {
return (<div className={css(Styles.loadingBar)}></div>)
}
render() {
return <div className={css(Styles.loadingBar)} />;
}
}

View File

@ -1,113 +1,124 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class MessageBytes extends Component {
static propTypes = {
seekTime: PropTypes.number.isRequired,
message: PropTypes.object.isRequired,
seekIndex: PropTypes.number,
live: PropTypes.bool.isRequired,
static propTypes = {
seekTime: PropTypes.number.isRequired,
message: PropTypes.object.isRequired,
seekIndex: PropTypes.number,
live: PropTypes.bool.isRequired
};
constructor(props) {
super(props);
this.state = {
isVisible: true,
lastMessageIndex: 0,
lastSeekTime: 0
};
constructor(props) {
super(props);
this.state = {
isVisible: true,
lastMessageIndex: 0,
lastSeekTime: 0,
};
this.onVisibilityChange = this.onVisibilityChange.bind(this);
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
}
this.onVisibilityChange = this.onVisibilityChange.bind(this);
this.onCanvasRefAvailable = this.onCanvasRefAvailable.bind(this);
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.live) {
const nextLastEntry =
nextProps.message.entries[nextProps.message.entries.length - 1];
const curLastEntry = this.props.message.entries[
this.props.message.entries.length - 1
];
return nextLastEntry.hexData !== curLastEntry.hexData;
} else {
return nextProps.seekTime !== this.props.seekTime;
}
}
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.live) {
const nextLastEntry = nextProps.message.entries[nextProps.message.entries.length - 1];
const curLastEntry = this.props.message.entries[this.props.message.entries.length - 1];
componentWillReceiveProps(nextProps) {
this.updateCanvas(nextProps);
}
return (nextLastEntry.hexData !== curLastEntry.hexData);
} else {
return nextProps.seekTime !== this.props.seekTime
findMostRecentMessage(seekTime) {
const { message } = this.props;
const { lastMessageIndex, lastSeekTime } = this.state;
let mostRecentMessageIndex = null;
if (seekTime >= lastSeekTime) {
for (let i = lastMessageIndex; i < message.entries.length; i++) {
const msg = message.entries[i];
if (msg && msg.relTime >= seekTime) {
mostRecentMessageIndex = i;
break;
}
}
}
componentWillReceiveProps(nextProps) {
this.updateCanvas(nextProps);
if (!mostRecentMessageIndex) {
// TODO this can be faster with binary search, not currently a bottleneck though.
mostRecentMessageIndex = message.entries.findIndex(
e => e.relTime >= seekTime
);
}
findMostRecentMessage(seekTime) {
const {message} = this.props;
const {lastMessageIndex, lastSeekTime} = this.state;
let mostRecentMessageIndex = null;
if(seekTime >= lastSeekTime) {
for(let i = lastMessageIndex; i < message.entries.length; i++) {
const msg = message.entries[i];
if(msg && msg.relTime >= seekTime) {
mostRecentMessageIndex = i;
break;
}
}
}
if (mostRecentMessageIndex) {
this.setState({
lastMessageIndex: mostRecentMessageIndex,
lastSeekTime: seekTime
});
return message.entries[mostRecentMessageIndex];
}
}
if(!mostRecentMessageIndex) {
// TODO this can be faster with binary search, not currently a bottleneck though.
updateCanvas(props) {
const { message, live, seekTime } = props;
if (!this.canvas || message.entries.length === 0) return;
mostRecentMessageIndex = message.entries.findIndex((e) => e.relTime >= seekTime);
}
let mostRecentMsg = message.entries[message.entries.length - 1];
if (!live) {
mostRecentMsg = this.findMostRecentMessage(seekTime);
if(mostRecentMessageIndex) {
this.setState({lastMessageIndex: mostRecentMessageIndex, lastSeekTime: seekTime});
return message.entries[mostRecentMessageIndex];
}
if (!mostRecentMsg) {
mostRecentMsg = message.entries[0];
}
}
updateCanvas(props) {
const {message, live, seekTime} = props;
if(!this.canvas || message.entries.length === 0) return;
const ctx = this.canvas.getContext("2d");
ctx.clearRect(0, 0, 180, 15);
for (let i = 0; i < message.byteStateChangeCounts.length; i++) {
const hexData = mostRecentMsg.hexData.substr(i * 2, 2);
ctx.fillStyle = message.byteColors[i];
let mostRecentMsg = message.entries[message.entries.length - 1];
if(!live) {
mostRecentMsg = this.findMostRecentMessage(seekTime);
ctx.fillRect(i * 20, 0, 20, 15);
if(!mostRecentMsg) {
mostRecentMsg = message.entries[0];
}
}
const ctx = this.canvas.getContext('2d');
ctx.clearRect(0,0,180,15);
for(let i = 0; i < message.byteStateChangeCounts.length; i++) {
const hexData = mostRecentMsg.hexData.substr(i * 2, 2);
ctx.fillStyle = message.byteColors[i];
ctx.fillRect(i * 20, 0, 20, 15);
ctx.font = '12px Courier';
ctx.fillStyle = 'white';
ctx.fillText(hexData, i * 20 + 2, 12);
}
ctx.font = "12px Courier";
ctx.fillStyle = "white";
ctx.fillText(hexData, i * 20 + 2, 12);
}
}
onVisibilityChange(isVisible) {
if(isVisible !== this.state.isVisible) {
this.setState({isVisible});
}
onVisibilityChange(isVisible) {
if (isVisible !== this.state.isVisible) {
this.setState({ isVisible });
}
}
onCanvasRefAvailable(ref) {
if(!ref) return;
onCanvasRefAvailable(ref) {
if (!ref) return;
this.canvas = ref;
this.canvas.width = 160 * window.devicePixelRatio;
this.canvas.height = 15 * window.devicePixelRatio;
const ctx = this.canvas.getContext('2d');
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
this.canvas = ref;
this.canvas.width = 160 * window.devicePixelRatio;
this.canvas.height = 15 * window.devicePixelRatio;
const ctx = this.canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
render() {
return (<canvas ref={this.onCanvasRefAvailable}
className='cabana-meta-messages-list-item-bytes-canvas'></canvas>);
}
render() {
return (
<canvas
ref={this.onCanvasRefAvailable}
className="cabana-meta-messages-list-item-bytes-canvas"
/>
);
}
}

View File

@ -1,9 +1,9 @@
import React, {Component} from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import { StyleSheet, css } from "aphrodite/no-important";
import PropTypes from "prop-types";
import GlobalStyles from '../styles/styles';
import Images from '../styles/images';
import GlobalStyles from "../styles/styles";
import Images from "../styles/images";
export default class Modal extends Component {
static propTypes = {
@ -21,112 +21,119 @@ export default class Modal extends Component {
}
_onKeyDown(e) {
if(e.keyCode === 27){ // escape
this.props.onCancel()
if (e.keyCode === 27) {
// escape
this.props.onCancel();
}
}
componentWillMount() {
document.addEventListener('keydown', this._onKeyDown);
document.addEventListener("keydown", this._onKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this._onKeyDown);
document.removeEventListener("keydown", this._onKeyDown);
}
selectButton() {
const {continueEnabled, continueText, onContinue} = this.props;
const { continueEnabled, continueText, onContinue } = this.props;
let style;
if(continueEnabled) {
if (continueEnabled) {
style = Styles.selectButtonEnabled;
} else {
style = Styles.selectButtonDisabled;
}
return (<div className={css(GlobalStyles.button, Styles.selectButton, style)}
onClick={continueEnabled ? this.props.onContinue : () => {}}>
<p>{continueText || 'Continue'}</p>
</div>);
return (
<div
className={css(GlobalStyles.button, Styles.selectButton, style)}
onClick={continueEnabled ? this.props.onContinue : () => {}}
>
<p>{continueText || "Continue"}</p>
</div>
);
}
render() {
return (<div className={css(Styles.root)}>
<div className={css(Styles.bg)}></div>
<div className={css(Styles.box)}>
<div className={css(Styles.header)}>
<p className={css(Styles.title)}>{this.props.title}</p>
<Images.clear styles={[Styles.closeButton]}
onClick={this.props.onCancel} />
</div>
{this.props.children}
<div className={css(Styles.select)}>
{this.selectButton()}
<div className={css(Styles.finishButton, Styles.cancelButton)}>
<p onClick={this.props.onCancel}>Cancel</p>
</div>
</div>
</div>
</div>);
return (
<div className={css(Styles.root)}>
<div className={css(Styles.bg)} />
<div className={css(Styles.box)}>
<div className={css(Styles.header)}>
<p className={css(Styles.title)}>{this.props.title}</p>
<Images.clear
styles={[Styles.closeButton]}
onClick={this.props.onCancel}
/>
</div>
{this.props.children}
<div className={css(Styles.select)}>
{this.selectButton()}
<div className={css(Styles.finishButton, Styles.cancelButton)}>
<p onClick={this.props.onCancel}>Cancel</p>
</div>
</div>
</div>
</div>
);
}
};
}
const Styles = StyleSheet.create({
bg: {
position: 'absolute',
position: "absolute",
left: 0,
top: 0,
zIndex: 9,
width: '100%',
height: '100%',
backgroundColor: 'white',
width: "100%",
height: "100%",
backgroundColor: "white",
opacity: 0.75
},
title: {
fontSize: 20,
marginBottom: 10,
marginRight: 'auto'
},
closeButton: {
marginRight: "auto"
},
closeButton: {},
box: {
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 10,
backgroundColor: 'white',
borderRadius: '4px',
border: '1px solid #000',
boxShadow: '1px 1px 1px #000',
padding: 20,
minWidth: 480
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
zIndex: 10,
backgroundColor: "white",
borderRadius: "4px",
border: "1px solid #000",
boxShadow: "1px 1px 1px #000",
padding: 20,
minWidth: 480
},
header: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end'
display: "flex",
flexDirection: "row",
justifyContent: "flex-end"
},
select: {
paddingTop: 20,
display: 'flex',
flexDirection: 'row'
display: "flex",
flexDirection: "row"
},
finishButton: {
borderRadius: 5,
height: 40,
width: 80,
cursor: 'pointer',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
cursor: "pointer",
display: "flex",
justifyContent: "center",
alignItems: "center"
},
selectButton: {
backgroundColor: 'rgb(77,144,254)',
color: 'white'
backgroundColor: "rgb(77,144,254)",
color: "white"
},
selectButtonDisabled: {
cursor: 'default',
cursor: "default",
opacity: 0.5
}
});
});

View File

@ -1,295 +1,376 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Moment from 'moment';
import _ from 'lodash';
import cx from 'classnames';
import React, { Component } from "react";
import PropTypes from "prop-types";
import Moment from "moment";
import _ from "lodash";
import cx from "classnames";
import auth from '../../api/comma-auth';
import auth from "../../api/comma-auth";
import Modal from '../Modals/baseModal';
import Modal from "../Modals/baseModal";
export default class OnboardingModal extends Component {
static propTypes = {
handlePandaConnect: PropTypes.func,
routes: PropTypes.array,
static propTypes = {
handlePandaConnect: PropTypes.func,
routes: PropTypes.array
};
static instructionalImages = {
step2: require("../../images/webusb-enable-experimental-features.png"),
step3: require("../../images/webusb-enable-webusb.png")
};
constructor(props) {
super(props);
this.state = {
webUsbEnabled: !!navigator.usb,
viewingUsbInstructions: false,
pandaConnected: false,
chffrDrivesSearch: "",
chffrDrivesSortBy: "start_time",
chffrDrivesOrderDesc: true
};
static instructionalImages = {
step2: require("../../images/webusb-enable-experimental-features.png"),
step3: require("../../images/webusb-enable-webusb.png"),
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
this.handleSortDrives = this.handleSortDrives.bind(this);
this.handleSearchDrives = this.handleSearchDrives.bind(this);
this.navigateToAuth = this.navigateToAuth.bind(this);
this.openChffrDrive = this.openChffrDrive.bind(this);
}
attemptPandaConnection() {
if (!this.state.webUsbEnabled) {
return;
}
this.props.handlePandaConnect();
}
constructor(props) {
super(props);
toggleUsbInstructions() {
this.setState({
viewingUsbInstructions: !this.state.viewingUsbInstructions
});
}
this.state = {
webUsbEnabled: !!navigator.usb,
viewingUsbInstructions: false,
pandaConnected: false,
chffrDrivesSearch: '',
chffrDrivesSortBy: 'start_time',
chffrDrivesOrderDesc: true,
}
navigateToAuth() {
const authUrl = auth.authUrl();
window.location.href = authUrl;
}
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
this.handleSortDrives = this.handleSortDrives.bind(this);
this.handleSearchDrives = this.handleSearchDrives.bind(this);
this.navigateToAuth = this.navigateToAuth.bind(this);
this.openChffrDrive = this.openChffrDrive.bind(this);
filterRoutesWithCan(drive) {
return drive.can === true;
}
handleSearchDrives(drive) {
const { chffrDrivesSearch } = this.state;
const searchKeywords = chffrDrivesSearch
.split(" ")
.filter(s => s.length > 0)
.map(s => s.toLowerCase());
return (
searchKeywords.length === 0 ||
searchKeywords.some(
kw =>
drive.end_geocode.toLowerCase().indexOf(kw) !== -1 ||
drive.start_geocode.toLowerCase().indexOf(kw) !== -1 ||
Moment(drive.start_time)
.format("dddd MMMM Do YYYY")
.toLowerCase()
.indexOf(kw) !== -1 ||
Moment(drive.end_time)
.format("dddd MMMM Do YYYY")
.toLowerCase()
.indexOf(kw) !== -1
)
);
}
handleSortDrives(key) {
if (this.state.chffrDrivesSortBy === key) {
this.setState({ chffrDrivesOrderDesc: !this.state.chffrDrivesOrderDesc });
} else {
this.setState({ chffrDrivesOrderDesc: true });
this.setState({ chffrDrivesSortBy: key });
}
}
attemptPandaConnection() {
if (!this.state.webUsbEnabled) { return };
this.props.handlePandaConnect();
openChffrDrive(route) {
window.location.search = `?route=${route.fullname}`;
}
renderPandaEligibility() {
const { webUsbEnabled, pandaConnected } = this.state;
const { attemptingPandaConnection } = this.props;
if (!webUsbEnabled) {
return (
<p>
<i className="fa fa-exclamation-triangle" />
<a onClick={this.toggleUsbInstructions}>
<span>WebUSB is not enabled in your Chrome settings</span>
</a>
</p>
);
} else if (!pandaConnected && attemptingPandaConnection) {
return (
<p>
<i className="fa fa-spinner animate-spin" />
<span className="animate-pulse-opacity">
Waiting for panda USB connection
</span>
</p>
);
}
}
toggleUsbInstructions() {
this.setState({ viewingUsbInstructions: !this.state.viewingUsbInstructions });
}
navigateToAuth() {
const authUrl = auth.authUrl();
window.location.href = authUrl;
}
filterRoutesWithCan(drive) {
return drive.can === true;
}
handleSearchDrives(drive) {
const { chffrDrivesSearch } = this.state;
const searchKeywords = chffrDrivesSearch.split(" ")
.filter((s) => s.length > 0)
.map((s) => s.toLowerCase());
return searchKeywords.length === 0 ||
searchKeywords.some((kw) => (
drive.end_geocode.toLowerCase().indexOf(kw) !== -1
|| drive.start_geocode.toLowerCase().indexOf(kw) !== -1
|| Moment(drive.start_time).format('dddd MMMM Do YYYY').toLowerCase().indexOf(kw) !== -1
|| Moment(drive.end_time).format('dddd MMMM Do YYYY').toLowerCase().indexOf(kw) !== -1));
}
handleSortDrives(key) {
if (this.state.chffrDrivesSortBy === key) {
this.setState({ chffrDrivesOrderDesc: !this.state.chffrDrivesOrderDesc });
} else {
this.setState({ chffrDrivesOrderDesc: true });
this.setState({ chffrDrivesSortBy: key });
}
}
openChffrDrive(route) {
window.location.search = `?route=${ route.fullname }`;
}
renderPandaEligibility() {
const { webUsbEnabled, pandaConnected } = this.state;
const { attemptingPandaConnection } = this.props;
if (!webUsbEnabled) {
return (
<p>
<i className='fa fa-exclamation-triangle'></i>
<a onClick={ this.toggleUsbInstructions }>
<span>WebUSB is not enabled in your Chrome settings</span>
</a>
</p>
)
}
else if (!pandaConnected && attemptingPandaConnection) {
return (
<p>
<i className='fa fa-spinner animate-spin'></i>
<span className='animate-pulse-opacity'>Waiting for panda USB connection</span>
</p>
)
}
}
renderChffrOption() {
const { routes } = this.props;
if (routes.length > 0) {
return (
<div className='cabana-onboarding-mode-chffr'>
<div className='cabana-onboarding-mode-chffr-search'>
<div className='form-field--small'>
<input type='text'
id='chffr_drives_search'
placeholder='Search chffr drives'
value={ this.state.chffrDrivesSearch }
onChange={ (e) =>
this.setState({ chffrDrivesSearch: e.target.value }) } />
<div className='cabana-onboarding-mode-chffr-search-helper'>
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
</div>
</div>
</div>
<div className={ cx('cabana-onboarding-mode-chffr-header', {
'is-ordered-desc': this.state.chffrDrivesOrderDesc,
'is-ordered-asc': !this.state.chffrDrivesOrderDesc
}) }>
<div className={ cx('cabana-onboarding-mode-chffr-drive-date', {
'is-sorted': this.state.chffrDrivesSortBy === 'start_time',
}) }
onClick={ () => this.handleSortDrives('start_time') }>
<span>Date</span>
</div>
<div className={ cx('cabana-onboarding-mode-chffr-drive-places', {
'is-sorted': this.state.chffrDrivesSortBy === 'end_geocode'
}) }
onClick={ () => this.handleSortDrives('end_geocode') }>
<span>Places</span>
</div>
<div className={ cx('cabana-onboarding-mode-chffr-drive-time') }>
<span>Time</span>
</div>
<div className={ cx('cabana-onboarding-mode-chffr-drive-distance', {
'is-sorted': this.state.chffrDrivesSortBy === 'len'
}) }
onClick={ () => this.handleSortDrives('len') }>
<span>Distance</span>
</div>
<div className='cabana-onboarding-mode-chffr-drive-action'></div>
</div>
<ul className='cabana-onboarding-mode-chffr-drives'>
{ _.orderBy(routes, [this.state.chffrDrivesSortBy], [this.state.chffrDrivesOrderDesc ? 'desc' : 'asc'])
.filter(this.filterRoutesWithCan)
.filter(this.handleSearchDrives)
.map((route) => {
const routeDuration = Moment.duration(route.end_time.diff(route.start_time));
const routeStartClock = Moment(route.start_time).format('LT');
const routeEndClock = Moment(route.end_time).format('LT');
return (
<li key={ route.fullname }
className='cabana-onboarding-mode-chffr-drive'>
<div className='cabana-onboarding-mode-chffr-drive-date'>
<strong>{ Moment(route.start_time._i).format('MMM Do') }</strong>
<span>{ Moment(route.start_time._i).format('dddd') }</span>
</div>
<div className='cabana-onboarding-mode-chffr-drive-places'>
<strong>{ route.end_geocode }</strong>
<span>From { route.start_geocode }</span>
</div>
<div className='cabana-onboarding-mode-chffr-drive-time'>
<strong>
{ routeDuration.hours > 0 ? `${ routeDuration._data.hours } hr ` : null }
{ `${ routeDuration._data.minutes } min ${ routeDuration._data.seconds } sec` }
</strong>
<span>{ `${ routeStartClock } - ${ routeEndClock }`}</span>
</div>
<div className='cabana-onboarding-mode-chffr-drive-distance'>
<strong>{ route.len.toFixed(2) } mi</strong>
<span>{ (route.len * 1.6).toFixed(2) } km</span>
</div>
<div className='cabana-onboarding-mode-chffr-drive-action'>
<button className='button--primary'
onClick={ () => this.openChffrDrive(route) }>
<span>View Drive</span>
</button>
</div>
</li>
)
})
}
</ul>
</div>
)
} else {
return (
<button onClick={ this.navigateToAuth }
className='button--primary button--kiosk'>
<i className='fa fa-video-camera'></i>
<strong>Log in to View Recorded Drives</strong>
<sup>Analyze your car driving data from <em>chffr</em></sup>
</button>
)
}
}
renderOnboardingOptions() {
return (
<div className='cabana-onboarding-modes'>
<div className='cabana-onboarding-mode'>
{ this.renderChffrOption() }
</div>
<div className='cabana-onboarding-mode'>
<button className={ cx('button--secondary button--kiosk', {
'is-disabled': !this.state.webUsbEnabled || this.props.attemptingPandaConnection
}) }
onClick={ this.attemptPandaConnection }>
<i className='fa fa-bolt'></i>
<strong>Launch Realtime Streaming</strong>
<sup>Interactively stream car data over USB with <em>panda</em></sup>
{ this.renderPandaEligibility() }
</button>
</div>
renderChffrOption() {
const { routes } = this.props;
if (routes.length > 0) {
return (
<div className="cabana-onboarding-mode-chffr">
<div className="cabana-onboarding-mode-chffr-search">
<div className="form-field--small">
<input
type="text"
id="chffr_drives_search"
placeholder="Search chffr drives"
value={this.state.chffrDrivesSearch}
onChange={e =>
this.setState({ chffrDrivesSearch: e.target.value })
}
/>
<div className="cabana-onboarding-mode-chffr-search-helper">
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
</div>
</div>
)
}
renderUsbInstructions() {
return (
<div className='cabana-onboarding-instructions'>
<button className='button--small button--inverted' onClick={ this.toggleUsbInstructions }>
<i className='fa fa-chevron-left'></i>
<span> Go back</span>
</button>
<h3>Follow these directions to enable WebUSB:</h3>
<ol className='cabana-onboarding-instructions-list list--bubbled'>
<li>
<p><strong>Open your Chrome settings:</strong></p>
<div className='inset'>
<span>chrome://flags/#enable-experimental-web-platform-features</span>
</div>
</li>
<li>
<p><strong>Enable Experimental Platform features:</strong></p>
<img alt={"Screenshot of Google Chrome Experimental Platform features"}
src={ OnboardingModal.instructionalImages.step2 } />
</li>
<li>
<p><strong>Enable WebUSB:</strong></p>
<img alt={"Screenshot of Google Chrome enable WebUSB"}
src={ OnboardingModal.instructionalImages.step3 } />
</li>
<li>
<p><strong>Relaunch your Chrome browser and try enabling live mode again.</strong></p>
</li>
</ol>
</div>
<div
className={cx("cabana-onboarding-mode-chffr-header", {
"is-ordered-desc": this.state.chffrDrivesOrderDesc,
"is-ordered-asc": !this.state.chffrDrivesOrderDesc
})}
>
<div
className={cx("cabana-onboarding-mode-chffr-drive-date", {
"is-sorted": this.state.chffrDrivesSortBy === "start_time"
})}
onClick={() => this.handleSortDrives("start_time")}
>
<span>Date</span>
</div>
)
<div
className={cx("cabana-onboarding-mode-chffr-drive-places", {
"is-sorted": this.state.chffrDrivesSortBy === "end_geocode"
})}
onClick={() => this.handleSortDrives("end_geocode")}
>
<span>Places</span>
</div>
<div className={cx("cabana-onboarding-mode-chffr-drive-time")}>
<span>Time</span>
</div>
<div
className={cx("cabana-onboarding-mode-chffr-drive-distance", {
"is-sorted": this.state.chffrDrivesSortBy === "len"
})}
onClick={() => this.handleSortDrives("len")}
>
<span>Distance</span>
</div>
<div className="cabana-onboarding-mode-chffr-drive-action" />
</div>
<ul className="cabana-onboarding-mode-chffr-drives">
{_.orderBy(
routes,
[this.state.chffrDrivesSortBy],
[this.state.chffrDrivesOrderDesc ? "desc" : "asc"]
)
.filter(this.filterRoutesWithCan)
.filter(this.handleSearchDrives)
.map(route => {
const routeDuration = Moment.duration(
route.end_time.diff(route.start_time)
);
const routeStartClock = Moment(route.start_time).format("LT");
const routeEndClock = Moment(route.end_time).format("LT");
return (
<li
key={route.fullname}
className="cabana-onboarding-mode-chffr-drive"
>
<div className="cabana-onboarding-mode-chffr-drive-date">
<strong>
{Moment(route.start_time._i).format("MMM Do")}
</strong>
<span>{Moment(route.start_time._i).format("dddd")}</span>
</div>
<div className="cabana-onboarding-mode-chffr-drive-places">
<strong>{route.end_geocode}</strong>
<span>From {route.start_geocode}</span>
</div>
<div className="cabana-onboarding-mode-chffr-drive-time">
<strong>
{routeDuration.hours > 0
? `${routeDuration._data.hours} hr `
: null}
{`${routeDuration._data.minutes} min ${
routeDuration._data.seconds
} sec`}
</strong>
<span>{`${routeStartClock} - ${routeEndClock}`}</span>
</div>
<div className="cabana-onboarding-mode-chffr-drive-distance">
<strong>{route.len.toFixed(2)} mi</strong>
<span>{(route.len * 1.6).toFixed(2)} km</span>
</div>
<div className="cabana-onboarding-mode-chffr-drive-action">
<button
className="button--primary"
onClick={() => this.openChffrDrive(route)}
>
<span>View Drive</span>
</button>
</div>
</li>
);
})}
</ul>
</div>
);
} else {
return (
<button
onClick={this.navigateToAuth}
className="button--primary button--kiosk"
>
<i className="fa fa-video-camera" />
<strong>Log in to View Recorded Drives</strong>
<sup>
Analyze your car driving data from <em>chffr</em>
</sup>
</button>
);
}
}
renderModalContent() {
if (this.state.viewingUsbInstructions) {
return this.renderUsbInstructions();
}
else {
return this.renderOnboardingOptions();
}
}
renderOnboardingOptions() {
return (
<div className="cabana-onboarding-modes">
<div className="cabana-onboarding-mode">{this.renderChffrOption()}</div>
<div className="cabana-onboarding-mode">
<button
className={cx("button--secondary button--kiosk", {
"is-disabled":
!this.state.webUsbEnabled ||
this.props.attemptingPandaConnection
})}
onClick={this.attemptPandaConnection}
>
<i className="fa fa-bolt" />
<strong>Launch Realtime Streaming</strong>
<sup>
Interactively stream car data over USB with <em>panda</em>
</sup>
{this.renderPandaEligibility()}
</button>
</div>
</div>
);
}
renderModalFooter() {
return (
renderUsbInstructions() {
return (
<div className="cabana-onboarding-instructions">
<button
className="button--small button--inverted"
onClick={this.toggleUsbInstructions}
>
<i className="fa fa-chevron-left" />
<span> Go back</span>
</button>
<h3>Follow these directions to enable WebUSB:</h3>
<ol className="cabana-onboarding-instructions-list list--bubbled">
<li>
<p>
<span>Don't have a <a href='https://panda.comma.ai' target='_blank'>panda</a>? </span>
<span><a href='https://panda.comma.ai' target='_blank'>Get one here</a> </span>
<span>or <a href='https://community.comma.ai/cabana/?demo=1'>try the demo</a>.</span>
<strong>Open your Chrome settings:</strong>
</p>
)
}
<div className="inset">
<span>
chrome://flags/#enable-experimental-web-platform-features
</span>
</div>
</li>
<li>
<p>
<strong>Enable Experimental Platform features:</strong>
</p>
<img
alt={"Screenshot of Google Chrome Experimental Platform features"}
src={OnboardingModal.instructionalImages.step2}
/>
</li>
<li>
<p>
<strong>Enable WebUSB:</strong>
</p>
<img
alt={"Screenshot of Google Chrome enable WebUSB"}
src={OnboardingModal.instructionalImages.step3}
/>
</li>
<li>
<p>
<strong>
Relaunch your Chrome browser and try enabling live mode again.
</strong>
</p>
</li>
</ol>
</div>
);
}
render() {
return (
<Modal
title='Welcome to cabana'
subtitle='Get started by viewing your chffr drives or enabling live mode'
footer={ this.renderModalFooter() }
disableClose={ true }
variations={['wide', 'dark']}>
{ this.renderModalContent() }
</Modal>
);
renderModalContent() {
if (this.state.viewingUsbInstructions) {
return this.renderUsbInstructions();
} else {
return this.renderOnboardingOptions();
}
}
renderModalFooter() {
return (
<p>
<span>
Don't have a{" "}
<a href="https://panda.comma.ai" target="_blank">
panda
</a>?{" "}
</span>
<span>
<a href="https://panda.comma.ai" target="_blank">
Get one here
</a>{" "}
</span>
<span>
or{" "}
<a href="https://community.comma.ai/cabana/?demo=1">try the demo</a>.
</span>
</p>
);
}
render() {
return (
<Modal
title="Welcome to cabana"
subtitle="Get started by viewing your chffr drives or enabling live mode"
footer={this.renderModalFooter()}
disableClose={true}
variations={["wide", "dark"]}
>
{this.renderModalContent()}
</Modal>
);
}
}

View File

@ -1,98 +1,99 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Measure from 'react-measure';
import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import Measure from "react-measure";
export default class Modal extends Component {
static PropTypes = {
variations: PropTypes.array,
disableClose: PropTypes.bool,
handleClose: PropTypes.func,
handleSave: PropTypes.func,
title: PropTypes.string,
subtitle: PropTypes.string,
navigation: PropTypes.node,
actions: PropTypes.node,
footer: PropTypes.node,
static PropTypes = {
variations: PropTypes.array,
disableClose: PropTypes.bool,
handleClose: PropTypes.func,
handleSave: PropTypes.func,
title: PropTypes.string,
subtitle: PropTypes.string,
navigation: PropTypes.node,
actions: PropTypes.node,
footer: PropTypes.node
};
constructor(props) {
super(props);
this.state = {
windowHeight: {},
modalHeight: {}
};
this.updateHeights = this.updateHeights.bind(this);
}
updateHeights(contentRect) {
this.setState({ windowHeight: window.innerHeight });
this.setState({ modalHeight: contentRect.bounds.height });
}
readVariationClasses() {
if (this.props.variations) {
const { variations } = this.props;
const classes = variations.reduce(
(classes, variation) => classes + `cabana-modal--${variation} `,
""
);
return classes;
}
}
constructor(props) {
super(props);
checkClosability() {
return this.props.disableClose || false;
}
this.state = {
windowHeight: {},
modalHeight: {},
};
checkYScrollability() {
return this.state.modalHeight > this.state.windowHeight;
}
this.updateHeights = this.updateHeights.bind(this);
}
updateHeights(contentRect) {
this.setState({ windowHeight: window.innerHeight });
this.setState({ modalHeight: contentRect.bounds.height });
}
readVariationClasses() {
if (this.props.variations) {
const { variations } = this.props;
const classes = variations.reduce((classes, variation) =>
classes + `cabana-modal--${variation} `,
'');
return classes;
}
}
checkClosability() {
return (this.props.disableClose || false);
}
checkYScrollability() {
return (this.state.modalHeight > this.state.windowHeight);
}
render() {
return (
<div className={ cx(
'cabana-modal',
this.readVariationClasses(), {
'cabana-modal--not-closable': this.checkClosability(),
'cabana-modal--scrollable-y': this.checkYScrollability(),
}) }>
<Measure
bounds
onResize={(contentRect) => {
this.updateHeights(contentRect);
}}>
{({ measureRef }) =>
<div ref={ measureRef } className='cabana-modal-container'>
<div className='cabana-modal-close-icon'
onClick={ this.props.handleClose }></div>
<div className='cabana-modal-header'>
<h1>{ this.props.title }</h1>
<p>{ this.props.subtitle }</p>
</div>
<div className='cabana-modal-navigation'>
{ this.props.navigation }
</div>
<div className='cabana-modal-body'>
<div className='cabana-modal-body-window'>
{ this.props.children }
</div>
<div className='cabana-modal-body-gradient'></div>
</div>
<div className='cabana-modal-actions'>
{ this.props.actions }
</div>
<div className='cabana-modal-footer'>
{ this.props.footer }
</div>
</div>
}
</Measure>
<div className='cabana-modal-backdrop'
onClick={ this.props.handleClose }>
render() {
return (
<div
className={cx("cabana-modal", this.readVariationClasses(), {
"cabana-modal--not-closable": this.checkClosability(),
"cabana-modal--scrollable-y": this.checkYScrollability()
})}
>
<Measure
bounds
onResize={contentRect => {
this.updateHeights(contentRect);
}}
>
{({ measureRef }) => (
<div ref={measureRef} className="cabana-modal-container">
<div
className="cabana-modal-close-icon"
onClick={this.props.handleClose}
/>
<div className="cabana-modal-header">
<h1>{this.props.title}</h1>
<p>{this.props.subtitle}</p>
</div>
<div className="cabana-modal-navigation">
{this.props.navigation}
</div>
<div className="cabana-modal-body">
<div className="cabana-modal-body-window">
{this.props.children}
</div>
<div className="cabana-modal-body-gradient" />
</div>
<div className="cabana-modal-actions">{this.props.actions}</div>
<div className="cabana-modal-footer">{this.props.footer}</div>
</div>
)
}
)}
</Measure>
<div
className="cabana-modal-backdrop"
onClick={this.props.handleClose}
/>
</div>
);
}
}

View File

@ -1,128 +1,137 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import PropTypes from "prop-types";
import {PART_SEGMENT_LENGTH} from '../config';
import { PART_SEGMENT_LENGTH } from "../config";
export default class PartSelector extends Component {
static selectorWidth = 150;
static propTypes = {
onPartChange: PropTypes.func.isRequired,
partsCount: PropTypes.number.isRequired,
static selectorWidth = 150;
static propTypes = {
onPartChange: PropTypes.func.isRequired,
partsCount: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.state = {
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
selectedPart: 0,
isDragging: false
};
constructor(props) {
super(props);
this.selectNextPart = this.selectNextPart.bind(this);
this.selectPrevPart = this.selectPrevPart.bind(this);
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
this.onSelectedPartMouseMove = this.onSelectedPartMouseMove.bind(this);
this.onSelectedPartDragEnd = this.onSelectedPartDragEnd.bind(this);
this.onClick = this.onClick.bind(this);
}
this.state = {
selectedPartStyle: this.makePartStyle(props.partsCount, 0),
selectedPart: 0,
isDragging: false,
};
makePartStyle(partsCount, selectedPart) {
return {
left: selectedPart / partsCount * PartSelector.selectorWidth,
width: PART_SEGMENT_LENGTH / partsCount * PartSelector.selectorWidth
};
}
this.selectNextPart = this.selectNextPart.bind(this);
this.selectPrevPart = this.selectPrevPart.bind(this);
this.onSelectedPartDragStart = this.onSelectedPartDragStart.bind(this);
this.onSelectedPartMouseMove = this.onSelectedPartMouseMove.bind(this);
this.onSelectedPartDragEnd = this.onSelectedPartDragEnd.bind(this);
this.onClick = this.onClick.bind(this);
componentWillReceiveProps(nextProps) {
if (nextProps.partsCount !== this.props.partsCount) {
const selectedPartStyle = this.makePartStyle(
nextProps.partsCount,
this.state.selectedPart
);
this.setState({ selectedPartStyle });
}
}
selectPart(part) {
part = Math.max(
0,
Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part)
);
if (part === this.state.selectedPart) {
return;
}
makePartStyle(partsCount, selectedPart) {
return {
left: (selectedPart / partsCount) * PartSelector.selectorWidth,
width: (PART_SEGMENT_LENGTH / partsCount) * PartSelector.selectorWidth
};
this.props.onPartChange(part);
this.setState({
selectedPart: part,
selectedPartStyle: this.makePartStyle(this.props.partsCount, part)
});
}
selectNextPart() {
let { selectedPart } = this.state;
selectedPart++;
if (selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
return;
}
componentWillReceiveProps(nextProps) {
if(nextProps.partsCount !== this.props.partsCount) {
const selectedPartStyle = this.makePartStyle(nextProps.partsCount, this.state.selectedPart);
this.setState({selectedPartStyle});
}
this.selectPart(selectedPart);
}
selectPrevPart() {
let { selectedPart } = this.state;
selectedPart--;
if (selectedPart < 0) {
return;
}
selectPart(part) {
part = Math.max(0, Math.min(this.props.partsCount - PART_SEGMENT_LENGTH, part));
if(part === this.state.selectedPart){
return;
}
this.selectPart(selectedPart);
}
this.props.onPartChange(part);
this.setState({selectedPart: part,
selectedPartStyle: this.makePartStyle(this.props.partsCount,
part)});
}
selectNextPart() {
let {selectedPart} = this.state;
selectedPart++;
if(selectedPart + PART_SEGMENT_LENGTH >= this.props.partsCount) {
return;
}
this.selectPart(selectedPart);
}
selectPrevPart() {
let {selectedPart} = this.state;
selectedPart--;
if(selectedPart < 0) {
return;
}
this.selectPart(selectedPart);
}
partAtClientX(clientX) {
const rect = this.selectorRect.getBoundingClientRect();
const x = clientX - rect.left;
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
}
onSelectedPartDragStart(e) {
this.setState({isDragging: true});
document.addEventListener('mouseup', this.onSelectedPartDragEnd);
}
onSelectedPartMouseMove(e) {
if(!this.state.isDragging) return;
const part = this.partAtClientX(e.clientX);
this.selectPart(part);
}
onSelectedPartDragEnd(e) {
this.setState({isDragging: false});
document.removeEventListener('mouseup', this.onSelectedPartDragEnd);
}
onClick(e) {
const part = this.partAtClientX(e.clientX);
this.selectPart(part);
}
render() {
const {selectedPartStyle} = this.state;
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
// all parts are available so no need to render the partselector
return null;
}
return (
<div className='cabana-explorer-part-selector'>
<div className='cabana-explorer-part-selector-track'
ref={ (selector) => this.selectorRect = selector }
style={{ width: PartSelector.selectorWidth }}
onMouseMove={ this.onSelectedPartMouseMove }
onClick={ this.onClick }>
<div className='cabana-explorer-part-selector-track-active'
style={ selectedPartStyle }
onMouseDown={ this.onSelectedPartDragStart }
onMouseUp={ this.onSelectedPartDragEnd }>
</div>
</div>
</div>
)
partAtClientX(clientX) {
const rect = this.selectorRect.getBoundingClientRect();
const x = clientX - rect.left;
return Math.floor(x * this.props.partsCount / PartSelector.selectorWidth);
}
onSelectedPartDragStart(e) {
this.setState({ isDragging: true });
document.addEventListener("mouseup", this.onSelectedPartDragEnd);
}
onSelectedPartMouseMove(e) {
if (!this.state.isDragging) return;
const part = this.partAtClientX(e.clientX);
this.selectPart(part);
}
onSelectedPartDragEnd(e) {
this.setState({ isDragging: false });
document.removeEventListener("mouseup", this.onSelectedPartDragEnd);
}
onClick(e) {
const part = this.partAtClientX(e.clientX);
this.selectPart(part);
}
render() {
const { selectedPartStyle } = this.state;
if (this.props.partsCount <= PART_SEGMENT_LENGTH) {
// all parts are available so no need to render the partselector
return null;
}
return (
<div className="cabana-explorer-part-selector">
<div
className="cabana-explorer-part-selector-track"
ref={selector => (this.selectorRect = selector)}
style={{ width: PartSelector.selectorWidth }}
onMouseMove={this.onSelectedPartMouseMove}
onClick={this.onClick}
>
<div
className="cabana-explorer-part-selector-track-active"
style={selectedPartStyle}
onMouseDown={this.onSelectedPartDragStart}
onMouseUp={this.onSelectedPartDragEnd}
/>
</div>
</div>
);
}
}

View File

@ -1,5 +1,5 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class PlayButton extends Component {
static propTypes = {
@ -11,33 +11,41 @@ export default class PlayButton extends Component {
constructor(props) {
super(props);
this.state = {'hover': false};
this.state = { hover: false };
this.onClick = this.onClick.bind(this);
}
imageSource() {
const {hover} = this.state;
const {isPlaying} = this.props;
if(isPlaying) {
if(hover) {
return (process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg");
const { hover } = this.state;
const { isPlaying } = this.props;
if (isPlaying) {
if (hover) {
return (
process.env.PUBLIC_URL + "/img/ic_pause_circle_filled_white_24px.svg"
);
} else {
return (process.env.PUBLIC_URL + "/img/ic_pause_circle_outline_white_24px.svg");
return (
process.env.PUBLIC_URL + "/img/ic_pause_circle_outline_white_24px.svg"
);
}
} else {
if(hover) {
return (process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg");
if (hover) {
return (
process.env.PUBLIC_URL + "/img/ic_play_circle_filled_white_24px.svg"
);
} else {
return (process.env.PUBLIC_URL + "/img/ic_play_circle_outline_white_24px.svg");
return (
process.env.PUBLIC_URL + "/img/ic_play_circle_outline_white_24px.svg"
);
}
}
}
onClick(e) {
let {isPlaying} = this.props;
let { isPlaying } = this.props;
if(!isPlaying) {
if (!isPlaying) {
this.props.onPlay();
} else {
this.props.onPause();
@ -45,11 +53,15 @@ export default class PlayButton extends Component {
}
render() {
return <img src={this.imageSource()}
alt={this.props.isPlaying ? 'Pause' : 'Play'}
className={this.props.className}
onClick={this.onClick}
onMouseOver={() => this.setState({hover: true})}
onMouseLeave={() => this.setState({hover: false})} />;
return (
<img
src={this.imageSource()}
alt={this.props.isPlaying ? "Pause" : "Play"}
className={this.props.className}
onClick={this.onClick}
onMouseOver={() => this.setState({ hover: true })}
onMouseLeave={() => this.setState({ hover: false })}
/>
);
}
}

View File

@ -1,220 +1,237 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import PlayButton from '../PlayButton';
import debounce from '../../utils/debounce';
import React, { Component } from "react";
import PropTypes from "prop-types";
import PlayButton from "../PlayButton";
import debounce from "../../utils/debounce";
export default class RouteSeeker extends Component {
static propTypes = {
secondsLoaded: PropTypes.number.isRequired,
segmentIndices: PropTypes.arrayOf(PropTypes.number),
onUserSeek: PropTypes.func,
onPlaySeek: PropTypes.func,
video: PropTypes.node,
onPause: PropTypes.func,
onPlay: PropTypes.func,
playing: PropTypes.bool,
segmentProgress: PropTypes.func,
ratioTime: PropTypes.func,
nearestFrameTime: PropTypes.number
static propTypes = {
secondsLoaded: PropTypes.number.isRequired,
segmentIndices: PropTypes.arrayOf(PropTypes.number),
onUserSeek: PropTypes.func,
onPlaySeek: PropTypes.func,
video: PropTypes.node,
onPause: PropTypes.func,
onPlay: PropTypes.func,
playing: PropTypes.bool,
segmentProgress: PropTypes.func,
ratioTime: PropTypes.func,
nearestFrameTime: PropTypes.number
};
static hiddenMarkerStyle = { display: "none", left: 0 };
static zeroSeekedBarStyle = { width: 0 };
static hiddenTooltipStyle = { display: "none", left: 0 };
static markerWidth = 20;
static tooltipWidth = 50;
constructor(props) {
super(props);
this.state = {
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
markerStyle: RouteSeeker.hiddenMarkerStyle,
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
ratio: 0,
tooltipTime: "0:00",
isPlaying: false,
isDragging: false
};
static hiddenMarkerStyle = {display: 'none', left: 0};
static zeroSeekedBarStyle = {width: 0};
static hiddenTooltipStyle = {display: 'none', left: 0};
static markerWidth = 20;
static tooltipWidth = 50;
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onClick = this.onClick.bind(this);
this.onPlay = this.onPlay.bind(this);
this.onPause = this.onPause.bind(this);
this.executePlayTimer = this.executePlayTimer.bind(this);
}
constructor(props) {
super(props);
this.state = {
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
markerStyle: RouteSeeker.hiddenMarkerStyle,
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
ratio: 0,
tooltipTime: '0:00',
isPlaying: false,
isDragging: false,
};
componentWillReceiveProps(nextProps) {
const { ratio } = this.state;
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onClick = this.onClick.bind(this);
this.onPlay = this.onPlay.bind(this);
this.onPause = this.onPause.bind(this);
this.executePlayTimer = this.executePlayTimer.bind(this);
if (
JSON.stringify(this.props.segmentIndices) !==
JSON.stringify(nextProps.segmentIndices)
) {
this.setState({
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
markerStyle: RouteSeeker.hiddenMarkerStyle,
ratio: 0
});
} else if (nextProps.secondsLoaded !== this.props.secondsLoaded) {
// adjust ratio in line with new secondsLoaded
const secondsSeeked = ratio * this.props.secondsLoaded;
const newRatio = secondsSeeked / nextProps.secondsLoaded;
this.updateSeekedBar(newRatio);
}
componentWillReceiveProps(nextProps) {
const {ratio} = this.state;
if(JSON.stringify(this.props.segmentIndices)
!== JSON.stringify(nextProps.segmentIndices)) {
this.setState({seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
markerStyle: RouteSeeker.hiddenMarkerStyle,
ratio: 0});
} else if(nextProps.secondsLoaded !== this.props.secondsLoaded) {
// adjust ratio in line with new secondsLoaded
const secondsSeeked = ratio * this.props.secondsLoaded;
const newRatio = secondsSeeked / nextProps.secondsLoaded;
this.updateSeekedBar(newRatio);
}
if(this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
this.updateSeekedBar(newRatio);
}
if(nextProps.playing && !this.state.isPlaying) {
this.onPlay();
} else if(!nextProps.playing && this.state.isPlaying) {
this.onPause();
}
if (this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
this.updateSeekedBar(newRatio);
}
componentWillUnmount() {
window.cancelAnimationFrame(this.playTimer);
if (nextProps.playing && !this.state.isPlaying) {
this.onPlay();
} else if (!nextProps.playing && this.state.isPlaying) {
this.onPause();
}
}
componentWillUnmount() {
window.cancelAnimationFrame(this.playTimer);
}
mouseEventXOffsetPercent(e) {
const rect = this.progressBar.getBoundingClientRect();
const x = e.clientX - rect.left;
return 100 * (x / this.progressBar.offsetWidth);
}
updateDraggingSeek = debounce(ratio => this.props.onUserSeek(ratio), 250);
onMouseMove(e) {
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
if (markerOffsetPct < 0) {
this.onMouseLeave();
return;
}
const markerWidth = RouteSeeker.markerWidth;
const markerLeft = `calc(${markerOffsetPct + "%"} - ${markerWidth / 2}px)`;
const markerStyle = {
display: "",
left: markerLeft
};
const tooltipWidth = RouteSeeker.tooltipWidth;
const tooltipLeft = `calc(${markerOffsetPct + "%"} - ${tooltipWidth /
2}px)`;
const tooltipStyle = { display: "flex", left: tooltipLeft };
const ratio = Math.max(0, markerOffsetPct / 100);
if (this.state.isDragging) {
this.updateSeekedBar(ratio);
this.updateDraggingSeek(ratio);
}
mouseEventXOffsetPercent(e) {
const rect = this.progressBar.getBoundingClientRect();
const x = e.clientX - rect.left;
this.setState({
markerStyle,
tooltipStyle,
tooltipTime: this.props.ratioTime(ratio).toFixed(3)
});
}
return 100 * (x / this.progressBar.offsetWidth);
onMouseLeave(e) {
this.setState({
markerStyle: RouteSeeker.hiddenMarkerStyle,
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
isDragging: false
});
}
updateSeekedBar(ratio) {
const seekedBarStyle = { width: 100 * ratio + "%" };
this.setState({ seekedBarStyle, ratio });
}
onClick(e) {
let ratio = this.mouseEventXOffsetPercent(e) / 100;
ratio = Math.min(1, Math.max(0, ratio));
this.updateSeekedBar(ratio);
this.props.onUserSeek(ratio);
}
onPlay() {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
let { ratio } = this.state;
if (ratio >= 1) {
ratio = 0;
}
this.setState({ isPlaying: true, ratio });
this.props.onPlay();
}
executePlayTimer() {
const { videoElement } = this.props;
if (videoElement === null) {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
return;
}
updateDraggingSeek = debounce((ratio) => this.props.onUserSeek(ratio), 250);
const { currentTime } = videoElement;
let newRatio = this.props.segmentProgress(currentTime);
onMouseMove(e) {
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
if(markerOffsetPct < 0) {
this.onMouseLeave();
return;
}
const markerWidth = RouteSeeker.markerWidth;
const markerLeft = `calc(${markerOffsetPct + '%'} - ${markerWidth / 2}px)`;
const markerStyle = {
display: '',
left: markerLeft
};
const tooltipWidth = RouteSeeker.tooltipWidth;
const tooltipLeft = `calc(${markerOffsetPct + '%'} - ${tooltipWidth / 2}px)`;
const tooltipStyle = { display: 'flex', left: tooltipLeft };
const ratio = Math.max(0, markerOffsetPct / 100);
if(this.state.isDragging) {
this.updateSeekedBar(ratio);
this.updateDraggingSeek(ratio);
}
this.setState({markerStyle,
tooltipStyle,
tooltipTime: this.props.ratioTime(ratio).toFixed(3)});
if (newRatio === this.state.ratio) {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
return;
}
onMouseLeave(e) {
this.setState({markerStyle: RouteSeeker.hiddenMarkerStyle,
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
isDragging: false});
if (newRatio >= 1) {
newRatio = 0;
this.props.onUserSeek(newRatio);
}
updateSeekedBar(ratio) {
const seekedBarStyle = { width: (100 * ratio) + '%' };
this.setState({seekedBarStyle, ratio})
if (newRatio >= 0) {
this.updateSeekedBar(newRatio);
this.props.onPlaySeek(currentTime);
}
onClick(e) {
let ratio = this.mouseEventXOffsetPercent(e) / 100;
ratio = Math.min(1, Math.max(0, ratio));
this.updateSeekedBar(ratio);
this.props.onUserSeek(ratio);
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
}
onPause() {
window.cancelAnimationFrame(this.playTimer);
this.setState({ isPlaying: false });
this.props.onPause();
}
onMouseDown() {
if (!this.state.isDragging) {
this.setState({ isDragging: true });
}
}
onPlay() {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
let {ratio} = this.state;
if(ratio >= 1) {
ratio = 0;
}
this.setState({isPlaying: true, ratio});
this.props.onPlay();
onMouseUp() {
if (this.state.isDragging) {
this.setState({ isDragging: false });
}
}
executePlayTimer() {
const {videoElement} = this.props;
if(videoElement === null) {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
return;
}
const {currentTime} = videoElement;
let newRatio = this.props.segmentProgress(currentTime);
if(newRatio === this.state.ratio) {
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
return;
}
if(newRatio >= 1) {
newRatio = 0;
this.props.onUserSeek(newRatio);
}
if(newRatio >= 0) {
this.updateSeekedBar(newRatio);
this.props.onPlaySeek(currentTime);
}
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
}
onPause() {
window.cancelAnimationFrame(this.playTimer);
this.setState({isPlaying: false});
this.props.onPause();
}
onMouseDown() {
if(!this.state.isDragging) {
this.setState({isDragging: true});
}
}
onMouseUp() {
if(this.state.isDragging) {
this.setState({isDragging: false});
}
}
render() {
const {seekedBarStyle, markerStyle, tooltipStyle} = this.state;
return (
<div className='cabana-explorer-visuals-camera-seeker'>
<PlayButton
className={'cabana-explorer-visuals-camera-seeker-playbutton'}
onPlay={this.onPlay}
onPause={this.onPause}
isPlaying={this.state.isPlaying} />
<div className={'cabana-explorer-visuals-camera-seeker-progress'}
onMouseMove={this.onMouseMove}
onMouseLeave={this.onMouseLeave}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onClick={this.onClick}
ref={(ref) => this.progressBar = ref}>
<div className={'cabana-explorer-visuals-camera-seeker-progress-tooltip'}
style={tooltipStyle}>
{this.state.tooltipTime}
</div>
<div className={'cabana-explorer-visuals-camera-seeker-progress-marker'}
style={markerStyle}></div>
<div className={'cabana-explorer-visuals-camera-seeker-progress-inner'}
style={seekedBarStyle}></div>
</div>
render() {
const { seekedBarStyle, markerStyle, tooltipStyle } = this.state;
return (
<div className="cabana-explorer-visuals-camera-seeker">
<PlayButton
className={"cabana-explorer-visuals-camera-seeker-playbutton"}
onPlay={this.onPlay}
onPause={this.onPause}
isPlaying={this.state.isPlaying}
/>
<div
className={"cabana-explorer-visuals-camera-seeker-progress"}
onMouseMove={this.onMouseMove}
onMouseLeave={this.onMouseLeave}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onClick={this.onClick}
ref={ref => (this.progressBar = ref)}
>
<div
className={"cabana-explorer-visuals-camera-seeker-progress-tooltip"}
style={tooltipStyle}
>
{this.state.tooltipTime}
</div>
);
}
<div
className={"cabana-explorer-visuals-camera-seeker-progress-marker"}
style={markerStyle}
/>
<div
className={"cabana-explorer-visuals-camera-seeker-progress-inner"}
style={seekedBarStyle}
/>
</div>
</div>
);
}
}

View File

@ -1,2 +1,2 @@
import RouteSeeker from './RouteSeeker';
export default RouteSeeker;
import RouteSeeker from "./RouteSeeker";
export default RouteSeeker;

View File

@ -1,194 +1,205 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, css } from 'aphrodite/no-important';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { StyleSheet, css } from "aphrodite/no-important";
import HLS from './HLS';
import {cameraPath} from '../api/routes';
import Video from '../api/video';
import RouteSeeker from './RouteSeeker/RouteSeeker';
import HLS from "./HLS";
import { cameraPath } from "../api/routes";
import Video from "../api/video";
import RouteSeeker from "./RouteSeeker/RouteSeeker";
const Styles = StyleSheet.create({
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 3
},
loadingSpinner: {
width: '25%',
height: '25%',
display: 'block'
},
img: {
height: 480,
display: 'block',
position: 'absolute',
zIndex: 2
},
hls: {
zIndex: 1,
height: 480,
backgroundColor: 'rgba(0,0,0,0.9)'
},
seekBar: {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
zIndex: 4
}
loadingOverlay: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 3
},
loadingSpinner: {
width: "25%",
height: "25%",
display: "block"
},
img: {
height: 480,
display: "block",
position: "absolute",
zIndex: 2
},
hls: {
zIndex: 1,
height: 480,
backgroundColor: "rgba(0,0,0,0.9)"
},
seekBar: {
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
zIndex: 4
}
});
export default class RouteVideoSync extends Component {
static propTypes = {
userSeekIndex: PropTypes.number.isRequired,
secondsLoaded: PropTypes.number.isRequired,
startOffset: PropTypes.number.isRequired,
message: PropTypes.object,
firstCanTime: PropTypes.number.isRequired,
canFrameOffset: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
playing: PropTypes.bool.isRequired,
onPlaySeek: PropTypes.func.isRequired,
onUserSeek: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onPause: PropTypes.func.isRequired,
userSeekTime: PropTypes.number.isRequired
static propTypes = {
userSeekIndex: PropTypes.number.isRequired,
secondsLoaded: PropTypes.number.isRequired,
startOffset: PropTypes.number.isRequired,
message: PropTypes.object,
firstCanTime: PropTypes.number.isRequired,
canFrameOffset: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
playing: PropTypes.bool.isRequired,
onPlaySeek: PropTypes.func.isRequired,
onUserSeek: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onPause: PropTypes.func.isRequired,
userSeekTime: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.state = {
shouldShowJpeg: true,
isLoading: true,
videoElement: null,
shouldRestartHls: false
};
constructor(props) {
super(props);
this.state = {
shouldShowJpeg: true,
isLoading: true,
videoElement: null,
shouldRestartHls: false,
};
this.onLoadStart = this.onLoadStart.bind(this);
this.onLoadEnd = this.onLoadEnd.bind(this);
this.segmentProgress = this.segmentProgress.bind(this);
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
this.onUserSeek = this.onUserSeek.bind(this);
this.onHlsRestart = this.onHlsRestart.bind(this);
this.ratioTime = this.ratioTime.bind(this);
}
this.onLoadStart = this.onLoadStart.bind(this);
this.onLoadEnd = this.onLoadEnd.bind(this);
this.segmentProgress = this.segmentProgress.bind(this);
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
this.onUserSeek = this.onUserSeek.bind(this);
this.onHlsRestart = this.onHlsRestart.bind(this);
this.ratioTime = this.ratioTime.bind(this);
componentWillReceiveProps(nextProps) {
if (
this.props.userSeekIndex !== nextProps.userSeekIndex ||
this.props.canFrameOffset !== nextProps.canFrameOffset ||
(this.props.message &&
nextProps.message &&
this.props.message.entries.length !== nextProps.message.entries.length)
) {
this.setState({ shouldRestartHls: true });
}
}
nearestFrameUrl() {
const { url } = this.props;
const sec = Math.round(this.props.userSeekTime);
return cameraPath(url, sec);
}
loadingOverlay() {
return (
<div className={css(Styles.loadingOverlay)}>
<img
className={css(Styles.loadingSpinner)}
src={process.env.PUBLIC_URL + "/img/loading.svg"}
alt={"Loading video"}
/>
</div>
);
}
onLoadStart() {
this.setState({
shouldShowJpeg: true,
isLoading: true
});
}
onLoadEnd() {
this.setState({
shouldShowJpeg: false,
isLoading: false
});
}
segmentProgress(currentTime) {
// returns progress as number in [0,1]
if (currentTime < this.props.startOffset) {
currentTime = this.props.startOffset;
}
componentWillReceiveProps(nextProps) {
if(this.props.userSeekIndex !== nextProps.userSeekIndex
|| this.props.canFrameOffset !== nextProps.canFrameOffset
|| (this.props.message
&& nextProps.message
&& this.props.message.entries.length !== nextProps.message.entries.length)) {
this.setState({shouldRestartHls: true});
}
const ratio =
(currentTime - this.props.startOffset) / this.props.secondsLoaded;
return Math.max(0, Math.min(1, ratio));
}
ratioTime(ratio) {
return ratio * this.props.secondsLoaded + this.props.startOffset;
}
onVideoElementAvailable(videoElement) {
this.setState({ videoElement });
}
onUserSeek(ratio) {
/* ratio in [0,1] */
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
if (ratio === 0) {
this.setState({ shouldRestartHls: true }, funcSeekToRatio);
} else {
funcSeekToRatio();
}
}
nearestFrameUrl() {
const {url} = this.props;
const sec = Math.round(this.props.userSeekTime);
return cameraPath(url, sec);
}
onHlsRestart() {
this.setState({ shouldRestartHls: false });
}
loadingOverlay() {
return (<div className={css(Styles.loadingOverlay)}>
<img className={css(Styles.loadingSpinner)}
src={process.env.PUBLIC_URL + "/img/loading.svg"}
alt={"Loading video"}
/>
</div>);
}
onLoadStart() {
this.setState({shouldShowJpeg: true,
isLoading: true});
}
onLoadEnd() {
this.setState({shouldShowJpeg: false,
isLoading: false});
}
segmentProgress(currentTime) {
// returns progress as number in [0,1]
if(currentTime < this.props.startOffset) {
currentTime = this.props.startOffset;
}
const ratio = (currentTime - this.props.startOffset) / this.props.secondsLoaded;
return Math.max(0, Math.min(1, ratio));
}
ratioTime(ratio) {
return ratio * this.props.secondsLoaded + this.props.startOffset;
}
onVideoElementAvailable(videoElement) {
this.setState({videoElement});
}
onUserSeek(ratio) {
/* ratio in [0,1] */
const funcSeekToRatio = () => this.props.onUserSeek(this.ratioTime(ratio));
if(ratio === 0) {
this.setState({shouldRestartHls: true},
funcSeekToRatio);
} else {
funcSeekToRatio();
}
}
onHlsRestart() {
this.setState({shouldRestartHls: false})
}
render() {
return (
<div className='cabana-explorer-visuals-camera'>
{this.state.isLoading ? this.loadingOverlay() : null}
{this.state.shouldShowJpeg ?
<img src={this.nearestFrameUrl()}
className={css(Styles.img)}
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)} />
: null }
<HLS
className={css(Styles.hls)}
source={Video.videoUrlForRouteUrl(this.props.url)}
startTime={this.props.userSeekTime}
playbackSpeed={1}
onVideoElementAvailable={this.onVideoElementAvailable}
playing={this.props.playing}
onClick={this.props.onVideoClick}
onLoadStart={this.onLoadStart}
onLoadEnd={this.onLoadEnd}
onUserSeek={this.onUserSeek}
onPlaySeek={this.props.onPlaySeek}
segmentProgress={this.segmentProgress}
shouldRestart={this.state.shouldRestartHls}
onRestart={this.onHlsRestart} />
<RouteSeeker
className={css(Styles.seekBar)}
nearestFrameTime={this.props.userSeekTime}
segmentProgress={this.segmentProgress}
secondsLoaded={this.props.secondsLoaded}
segmentIndices={this.props.segmentIndices}
onUserSeek={this.onUserSeek}
onPlaySeek={this.props.onPlaySeek}
videoElement={this.state.videoElement}
onPlay={this.props.onPlay}
onPause={this.props.onPause}
playing={this.props.playing}
ratioTime={this.ratioTime} />
</div>
);
}
render() {
return (
<div className="cabana-explorer-visuals-camera">
{this.state.isLoading ? this.loadingOverlay() : null}
{this.state.shouldShowJpeg ? (
<img
src={this.nearestFrameUrl()}
className={css(Styles.img)}
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)}
/>
) : null}
<HLS
className={css(Styles.hls)}
source={Video.videoUrlForRouteUrl(this.props.url)}
startTime={this.props.userSeekTime}
playbackSpeed={1}
onVideoElementAvailable={this.onVideoElementAvailable}
playing={this.props.playing}
onClick={this.props.onVideoClick}
onLoadStart={this.onLoadStart}
onLoadEnd={this.onLoadEnd}
onUserSeek={this.onUserSeek}
onPlaySeek={this.props.onPlaySeek}
segmentProgress={this.segmentProgress}
shouldRestart={this.state.shouldRestartHls}
onRestart={this.onHlsRestart}
/>
<RouteSeeker
className={css(Styles.seekBar)}
nearestFrameTime={this.props.userSeekTime}
segmentProgress={this.segmentProgress}
secondsLoaded={this.props.secondsLoaded}
segmentIndices={this.props.segmentIndices}
onUserSeek={this.onUserSeek}
onPlaySeek={this.props.onPlaySeek}
videoElement={this.state.videoElement}
onPlay={this.props.onPlay}
onPause={this.props.onPause}
playing={this.props.playing}
ratioTime={this.ratioTime}
/>
</div>
);
}
}

View File

@ -1,227 +1,227 @@
import React, {Component} from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';
import FileSaver from 'file-saver';
import React, { Component } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import FileSaver from "file-saver";
import OpenDbc from '../api/OpenDbc';
import DBC from '../models/can/dbc';
import Modal from './Modals/baseModal';
import OpenDbc from "../api/OpenDbc";
import DBC from "../models/can/dbc";
import Modal from "./Modals/baseModal";
// import TabStyles from '../styles/modal-tabs';
export default class SaveDbcModal extends Component {
static propTypes = {
dbc: PropTypes.instanceOf(DBC).isRequired,
sourceDbcFilename: PropTypes.string.isRequired,
handleClose: PropTypes.func.isRequired,
onDbcSaved: PropTypes.func.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
hasGithubAuth: PropTypes.bool.isRequired,
loginWithGithub: PropTypes.element.isRequired
static propTypes = {
dbc: PropTypes.instanceOf(DBC).isRequired,
sourceDbcFilename: PropTypes.string.isRequired,
handleClose: PropTypes.func.isRequired,
onDbcSaved: PropTypes.func.isRequired,
openDbcClient: PropTypes.instanceOf(OpenDbc).isRequired,
hasGithubAuth: PropTypes.bool.isRequired,
loginWithGithub: PropTypes.element.isRequired
};
constructor(props) {
super(props);
this.state = {
tab: "GitHub",
openDbcFork: null,
dbcFilename: this.props.sourceDbcFilename,
tabs: ["GitHub", "Download"]
};
constructor(props) {
super(props);
this.state = {
tab: 'GitHub',
openDbcFork: null,
dbcFilename: this.props.sourceDbcFilename,
tabs: ['GitHub', 'Download'],
};
this.commitToGitHub = this.commitToGitHub.bind(this);
this.downloadDbcFile = this.downloadDbcFile.bind(this);
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
this.renderForkButton = this.renderForkButton.bind(this);
this.renderTabNavigation = this.renderTabNavigation.bind(this);
this.renderActions = this.renderActions.bind(this);
}
this.commitToGitHub = this.commitToGitHub.bind(this);
this.downloadDbcFile = this.downloadDbcFile.bind(this);
this.forkOpenDbcAndWait = this.forkOpenDbcAndWait.bind(this);
this.renderForkButton = this.renderForkButton.bind(this);
this.renderTabNavigation = this.renderTabNavigation.bind(this);
this.renderActions = this.renderActions.bind(this);
async componentWillMount() {
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
this.setState({ openDbcFork });
}
async commitToGitHub() {
const { openDbcFork, dbcFilename } = this.state;
const filename = dbcFilename.replace(/\.dbc/g, "") + ".dbc";
const success = await this.props.openDbcClient.commitFile(
openDbcFork,
filename,
this.props.dbc.text()
);
if (success) {
this.props.onDbcSaved(filename);
}
}
async componentWillMount() {
const openDbcFork = await this.props.openDbcClient.getUserOpenDbcFork();
this.setState({openDbcFork})
}
async downloadDbcFile() {
const blob = new Blob([this.props.dbc.text()], {
type: "text/plain;charset=utf-8"
});
const filename = this.state.dbcFilename.replace(/\.dbc/g, "") + ".dbc";
FileSaver.saveAs(blob, filename, true);
}
async commitToGitHub() {
const { openDbcFork, dbcFilename } = this.state;
const filename = dbcFilename.replace(/\.dbc/g, '') + '.dbc';
const success = await this.props.openDbcClient.commitFile(openDbcFork,
filename,
this.props.dbc.text());
if (success) {
this.props.onDbcSaved(filename);
}
}
async downloadDbcFile() {
const blob = new Blob([this.props.dbc.text()], {type: "text/plain;charset=utf-8"});
const filename = this.state.dbcFilename.replace(/\.dbc/g, '') + '.dbc';
FileSaver.saveAs(blob, filename, true);
}
async forkOpenDbcAndWait() {
const forkResponseSuccess = await this.props.openDbcClient.fork();
if(forkResponseSuccess) {
let isTimedOut = false;
const timeout = window.setTimeout(() => {
isTimedOut = true;
}, 30000);
const interval = window.setInterval(() => {
if(!isTimedOut) {
this.props.openDbcClient.getUserOpenDbcFork().then((openDbcFork) => {
if(openDbcFork !== null) {
this.setState({openDbcFork});
window.clearInterval(interval);
window.clearTimeout(timeout);
}
});
} else {
window.clearInterval(interval);
}
}, 3000);
async forkOpenDbcAndWait() {
const forkResponseSuccess = await this.props.openDbcClient.fork();
if (forkResponseSuccess) {
let isTimedOut = false;
const timeout = window.setTimeout(() => {
isTimedOut = true;
}, 30000);
const interval = window.setInterval(() => {
if (!isTimedOut) {
this.props.openDbcClient.getUserOpenDbcFork().then(openDbcFork => {
if (openDbcFork !== null) {
this.setState({ openDbcFork });
window.clearInterval(interval);
window.clearTimeout(timeout);
}
});
} else {
// fork failed
window.clearInterval(interval);
}
}, 3000);
} else {
// fork failed
}
}
primaryActionDisabled() {
const { tab } = this.state;
if (tab === 'GitHub') {
return this.state.openDbcFork != null
&& this.state.dbcFilename.length > 0
} else if (tab === 'Download') {
return true;
}
}
renderForkButton() {
return (
<button onClick={ this.forkOpenDbcAndWait }>
<i className='fa fa-code-fork'></i>
<span> Fork OpenDBC</span>
</button>
);
}
renderForkStep() {
const { openDbcFork } = this.state;
let content;
if (openDbcFork !== null) {
content = (
<button disabled>
<i className='fa fa-code-fork'></i>
<span> Forked: { openDbcFork }</span>
</button>
)
} else if (this.props.hasGithubAuth) {
content = this.renderForkButton();
} else {
content = this.props.loginWithGithub;
}
return (
<div>
{ openDbcFork !== null ? this.renderForkButton() : null }
{ content }
<hr />
</div>
);
}
renderFilenameField() {
return (
<div className='form-field' data-extension='.dbc'>
<label htmlFor='filename'>
<span>Choose a filename:</span>
<sup>Pick a unique name for your car DBC file</sup>
</label>
<input type='text'
id='filename'
value={ this.state.dbcFilename.replace(/\.dbc/g, '') }
size={ this.state.dbcFilename.length + 2 }
onChange={ (e) =>
this.setState({dbcFilename: e.target.value}) } />
</div>
);
}
renderTabNavigation() {
primaryActionDisabled() {
const { tab } = this.state;
if (tab === "GitHub") {
return (
<div className='cabana-tabs-navigation'>
{ this.state.tabs.map((tab) => {
return (
<a className={ cx({'is-active': this.state.tab === tab})}
onClick={ () => { this.setState({ tab }) }}
key={tab}>
<span>{ tab }</span>
</a>
)
})}
this.state.openDbcFork != null && this.state.dbcFilename.length > 0
);
} else if (tab === "Download") {
return true;
}
}
renderForkButton() {
return (
<button onClick={this.forkOpenDbcAndWait}>
<i className="fa fa-code-fork" />
<span> Fork OpenDBC</span>
</button>
);
}
renderForkStep() {
const { openDbcFork } = this.state;
let content;
if (openDbcFork !== null) {
content = (
<button disabled>
<i className="fa fa-code-fork" />
<span> Forked: {openDbcFork}</span>
</button>
);
} else if (this.props.hasGithubAuth) {
content = this.renderForkButton();
} else {
content = this.props.loginWithGithub;
}
return (
<div>
{openDbcFork !== null ? this.renderForkButton() : null}
{content}
<hr />
</div>
);
}
renderFilenameField() {
return (
<div className="form-field" data-extension=".dbc">
<label htmlFor="filename">
<span>Choose a filename:</span>
<sup>Pick a unique name for your car DBC file</sup>
</label>
<input
type="text"
id="filename"
value={this.state.dbcFilename.replace(/\.dbc/g, "")}
size={this.state.dbcFilename.length + 2}
onChange={e => this.setState({ dbcFilename: e.target.value })}
/>
</div>
);
}
renderTabNavigation() {
return (
<div className="cabana-tabs-navigation">
{this.state.tabs.map(tab => {
return (
<a
className={cx({ "is-active": this.state.tab === tab })}
onClick={() => {
this.setState({ tab });
}}
key={tab}
>
<span>{tab}</span>
</a>
);
})}
</div>
);
}
renderTabContent() {
const { tab } = this.state;
if (tab === "GitHub") {
return (
<div>
{this.renderForkStep()}
{this.renderFilenameField()}
</div>
)
);
} else if (tab === "Download") {
return <div>{this.renderFilenameField()}</div>;
}
}
renderTabContent() {
const { tab } = this.state;
if (tab === 'GitHub') {
return (
<div>
{ this.renderForkStep() }
{ this.renderFilenameField() }
</div>
);
}
else if (tab === 'Download') {
return (
<div>
{ this.renderFilenameField() }
</div>
);
}
renderActions() {
const { tab } = this.state;
if (tab === "GitHub") {
return (
<div>
<button className="button--inverted" onClick={this.props.handleClose}>
<span>Cancel</span>
</button>
<button className="button--primary" onClick={this.commitToGitHub}>
<span>Commit to GitHub</span>
</button>
</div>
);
} else if (tab === "Download") {
return (
<div>
<button className="button--inverted" onClick={this.props.handleClose}>
<span>Cancel</span>
</button>
<button className="button--primary" onClick={this.downloadDbcFile}>
<span>Download</span>
</button>
</div>
);
}
}
renderActions() {
const { tab } = this.state;
if (tab === 'GitHub') {
return (
<div>
<button className='button--inverted'
onClick={ this.props.handleClose }>
<span>Cancel</span>
</button>
<button className='button--primary'
onClick={ this.commitToGitHub }>
<span>Commit to GitHub</span>
</button>
</div>
)
}
else if (tab === 'Download') {
return (
<div>
<button className='button--inverted'
onClick={ this.props.handleClose }>
<span>Cancel</span>
</button>
<button className='button--primary'
onClick={ this.downloadDbcFile }>
<span>Download</span>
</button>
</div>
)
}
}
render() {
return (
<Modal
title='Save DBC File'
subtitle='Save your progress and output to a DBC file'
handleClose={ this.props.handleClose }
navigation={ this.renderTabNavigation() }
actions={ this.renderActions() }>
{ this.renderTabContent() }
</Modal>
);
}
render() {
return (
<Modal
title="Save DBC File"
subtitle="Save your progress and output to a DBC file"
handleClose={this.props.handleClose}
navigation={this.renderTabNavigation()}
actions={this.renderActions()}
>
{this.renderTabContent()}
</Modal>
);
}
}

View File

@ -1,365 +1,398 @@
// SignalLegendEntry.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import React, { Component } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import Signal from '../models/can/signal';
import DbcUtils from '../utils/dbc';
import {swapKeysAndValues} from '../utils/object';
import Signal from "../models/can/signal";
import DbcUtils from "../utils/dbc";
import { swapKeysAndValues } from "../utils/object";
export default class SignalLegendEntry extends Component {
static propTypes = {
signal: PropTypes.instanceOf(Signal).isRequired,
isHighlighted: PropTypes.bool,
onSignalHover: PropTypes.func,
onSignalHoverEnd: PropTypes.func,
onTentativeSignalChange: PropTypes.func,
onSignalChange: PropTypes.func,
onSignalRemove: PropTypes.func,
onSignalPlotChange: PropTypes.func,
toggleExpandSignal: PropTypes.func,
isPlotted: PropTypes.bool,
isExpanded: PropTypes.bool,
static propTypes = {
signal: PropTypes.instanceOf(Signal).isRequired,
isHighlighted: PropTypes.bool,
onSignalHover: PropTypes.func,
onSignalHoverEnd: PropTypes.func,
onTentativeSignalChange: PropTypes.func,
onSignalChange: PropTypes.func,
onSignalRemove: PropTypes.func,
onSignalPlotChange: PropTypes.func,
toggleExpandSignal: PropTypes.func,
isPlotted: PropTypes.bool,
isExpanded: PropTypes.bool
};
static unsignedTransformation = field => {
return (value, signal) => {
if (value !== "") {
value = Number(value) || 0;
if (value < 0) {
value = 0;
}
}
signal[field] = value;
return signal;
};
};
static unsignedTransformation = (field) => {
return (value, signal) => {
if(value !== '') {
value = Number(value) || 0;
static fields = [
{
field: "name",
title: "Name",
type: "string"
},
{
field: "size",
title: "Size",
type: "number",
transform: SignalLegendEntry.unsignedTransformation("size")
},
{
field: "startBit",
title: signal =>
signal.isLittleEndian
? "Least significant bit"
: "Most significant bit",
type: "number",
transform: SignalLegendEntry.unsignedTransformation("startBit")
},
{
field: "isLittleEndian",
title: "Endianness",
type: "option",
options: {
options: ["Little", "Big"],
optionValues: { Little: true, Big: false }
},
transform: (isLittleEndian, signal) => {
if (signal.isLittleEndian !== isLittleEndian) {
const { startBit } = signal;
if(value < 0) {
value = 0;
}
if (isLittleEndian) {
// big endian -> little endian
const startByte = Math.floor(signal.startBit / 8),
endByte = Math.floor((signal.startBit - signal.size + 1) / 8);
if (startByte === endByte) {
signal.startBit = signal.startBit - signal.size + 1;
} else {
signal.startBit = DbcUtils.matrixBitNumber(startBit);
}
signal[field] = value;
return signal;
};
};
} else {
// little endian -> big endian
const startByte = Math.floor(signal.startBit / 8),
endByte = Math.floor((signal.startBit + signal.size - 1) / 8);
static fields = [
{
field: 'name',
title: 'Name',
type: 'string',
},
{
field: 'size',
title: 'Size',
type: 'number',
transform: SignalLegendEntry.unsignedTransformation('size')
},
{
field: 'startBit',
title: (signal) => signal.isLittleEndian ? 'Least significant bit' : 'Most significant bit',
type: 'number',
transform: SignalLegendEntry.unsignedTransformation('startBit')
},
{
field: 'isLittleEndian',
title: 'Endianness',
type: 'option',
options: {
options: ['Little', 'Big'],
optionValues: {Little: true, Big: false}
},
transform: (isLittleEndian, signal) => {
if(signal.isLittleEndian !== isLittleEndian) {
const {startBit} = signal;
if(isLittleEndian) {
// big endian -> little endian
const startByte = Math.floor(signal.startBit / 8),
endByte = Math.floor((signal.startBit - signal.size + 1) / 8);
if(startByte === endByte) {
signal.startBit = signal.startBit - signal.size + 1;
} else {
signal.startBit = DbcUtils.matrixBitNumber(startBit);
}
} else {
// little endian -> big endian
const startByte = Math.floor(signal.startBit / 8),
endByte = Math.floor((signal.startBit + signal.size - 1) / 8);
if(startByte === endByte) {
signal.startBit = signal.startBit + signal.size - 1;
} else {
signal.startBit = DbcUtils.bigEndianBitIndex(startBit);
}
}
signal.isLittleEndian = isLittleEndian;
}
return signal;
if (startByte === endByte) {
signal.startBit = signal.startBit + signal.size - 1;
} else {
signal.startBit = DbcUtils.bigEndianBitIndex(startBit);
}
},
{
field: 'isSigned',
title: 'Sign',
type: 'option',
options: {options: ['Signed', 'Unsigned'],
optionValues: {Signed: true, Unsigned: false}}
},
{
field: 'factor',
title: 'Factor',
type: 'number'
},
{
field: 'offset',
title: 'Offset',
type: 'number'
},
{
field: 'unit',
title: 'Unit',
type: 'string'
},
{
field: 'comment',
title: 'Comment',
type: 'string'
},
{
field: 'min',
title: 'Minimum value',
type: 'number'
},
{
field: 'max',
title: 'Maximum value',
type: 'number'
}
signal.isLittleEndian = isLittleEndian;
}
];
return signal;
}
},
{
field: "isSigned",
title: "Sign",
type: "option",
options: {
options: ["Signed", "Unsigned"],
optionValues: { Signed: true, Unsigned: false }
}
},
{
field: "factor",
title: "Factor",
type: "number"
},
{
field: "offset",
title: "Offset",
type: "number"
},
{
field: "unit",
title: "Unit",
type: "string"
},
{
field: "comment",
title: "Comment",
type: "string"
},
{
field: "min",
title: "Minimum value",
type: "number"
},
{
field: "max",
title: "Maximum value",
type: "number"
}
];
static fieldSpecForName = (name) => {
return SignalLegendEntry.fields.find((field) =>
field.field === name)
static fieldSpecForName = name => {
return SignalLegendEntry.fields.find(field => field.field === name);
};
constructor(props) {
super(props);
this.state = {
isExpanded: false,
signalEdited: Object.assign(Object.create(props.signal), props.signal)
};
constructor(props) {
super(props);
this.state = {
isExpanded: false,
signalEdited: Object.assign(Object.create(props.signal), props.signal),
};
this.toggleEditing = this.toggleEditing.bind(this);
this.updateField = this.updateField.bind(this);
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
}
this.toggleEditing = this.toggleEditing.bind(this);
this.updateField = this.updateField.bind(this);
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
}
componentWillReceiveProps(nextProps) {
if(!nextProps.signal.equals(this.props.signal)) {
this.setState({signalEdited: Object.assign(Object.create(nextProps.signal),
nextProps.signal)});
}
}
renderField(field, title, valueCol, signal) {
let titleStr;
if(typeof title === 'function') {
titleStr = title(signal);
} else {
titleStr = title;
}
return (
<div key={field} className='form-field form-field--small'>
<label htmlFor={`${signal.name}_${field}`}>{titleStr}</label>
{valueCol}
</div>
componentWillReceiveProps(nextProps) {
if (!nextProps.signal.equals(this.props.signal)) {
this.setState({
signalEdited: Object.assign(
Object.create(nextProps.signal),
nextProps.signal
)
});
}
}
renderField(field, title, valueCol, signal) {
let titleStr;
if (typeof title === "function") {
titleStr = title(signal);
} else {
titleStr = title;
}
updateField(fieldSpec, value) {
let {signalEdited} = this.state;
const {signal} = this.props;
return (
<div key={field} className="form-field form-field--small">
<label htmlFor={`${signal.name}_${field}`}>{titleStr}</label>
{valueCol}
</div>
);
}
if(fieldSpec.transform) {
signalEdited = fieldSpec.transform(value, signalEdited);
} else {
signalEdited[fieldSpec.field] = value;
}
updateField(fieldSpec, value) {
let { signalEdited } = this.state;
const { signal } = this.props;
// Save entire signal while editing
this.setState({signalEdited});
const signalCopy = Object.assign(Object.create(signal), signal);
Object.entries(signalEdited).forEach(([field, value]) => {
signalCopy[field] = value;
});
this.props.onSignalChange(signalCopy, signal);
if (fieldSpec.transform) {
signalEdited = fieldSpec.transform(value, signalEdited);
} else {
signalEdited[fieldSpec.field] = value;
}
renderNumberField(fieldSpec, signal) {
const {field, title} = fieldSpec;
let valueCol;
// Save entire signal while editing
this.setState({ signalEdited });
const signalCopy = Object.assign(Object.create(signal), signal);
Object.entries(signalEdited).forEach(([field, value]) => {
signalCopy[field] = value;
});
this.props.onSignalChange(signalCopy, signal);
}
if(this.props.isExpanded) {
let value = this.state.signalEdited[field];
if(value !== '') {
let num = Number(value);
value = (isNaN(num) ? '' : num);
}
valueCol = (
<input id={`${signal.name}_${field}`}
type="number"
value={value}
onChange={(e) => {this.updateField(fieldSpec, e.target.value)}
}/>
);
} else {
let value = this.props.signal[field];
valueCol = <span>{value}</span>;
}
return this.renderField(field, title, valueCol, signal);
}
renderNumberField(fieldSpec, signal) {
const { field, title } = fieldSpec;
let valueCol;
renderStringField(fieldSpec, signal) {
const {field, title} = fieldSpec;
let valueCol;
if(this.props.isExpanded) {
valueCol = (
<input id={`${signal.name}_${field}`}
type="text"
value={this.state.signalEdited[field] || ''}
onChange={(e) => {
this.updateField(fieldSpec, e.target.value)
}}
/>);
} else {
valueCol = <span>{this.props.signal[field]}</span>;
}
return this.renderField(field, title, valueCol, signal);
}
renderOptionField(fieldSpec, signal) {
let valueCol;
const {field, title} = fieldSpec;
const {options, optionValues} = fieldSpec.options;
let valueOptions = swapKeysAndValues(optionValues);
if(this.props.isExpanded) {
const optionEles = options.map((opt) =>
<option key={opt}
value={optionValues[opt]}>{opt}</option>
);
valueCol = (
<select id={`${signal.name}_${field}`}
defaultValue={this.state.signalEdited[field]}
onChange={
(e) => { this.updateField(fieldSpec, e.target.value === "true") }
}>
{optionEles}
</select>
);
} else {
valueCol = <span>{valueOptions[this.props.signal[field]]}</span>;
}
return this.renderField(field, title, valueCol, signal);
}
renderFieldNode(field, signal) {
if(field.type === 'number') {
return this.renderNumberField(field, signal);
} else if(field.type === 'option') {
return this.renderOptionField(field, signal);
} else if(field.type === 'string') {
return this.renderStringField(field, signal);
}
}
toggleEditing(e) {
let { signalEdited } = this.state;
const { signal, isExpanded } = this.props;
const signalCopy = Object.assign(Object.create(signal), signal);
if(isExpanded) {
// Finished editing, save changes & reset intermediate
// signalEdited state.
Object.entries(signalEdited).forEach(([field, value]) => {
const fieldSpec = SignalLegendEntry.fieldSpecForName(field);
if(fieldSpec && fieldSpec.type === 'number' && isNaN(parseInt(value, 10))) {
value = 0;
}
signalCopy[field] = value;
});
this.props.onSignalChange(signalCopy, signal);
} else {
signalEdited = signalCopy;
}
// Expand and enable signal editing
this.setState({
signalEdited
})
this.props.toggleExpandSignal(signal);
e.stopPropagation();
}
renderSignalForm(signal) {
return (
<div className='signals-legend-entry-form'>
{SignalLegendEntry.fields.map((field) => {
return (
<div className='signals-legend-entry-form-field'
key={field.field}>
{this.renderFieldNode(field, signal)}
</div>
)
})}
<div className='signals-legend-entry-form-remove'>
<button className='button--tiny button--alpha'
onClick={ () => this.props.onSignalRemove(signal) }>Remove Signal</button>
</div>
</div>
if (this.props.isExpanded) {
let value = this.state.signalEdited[field];
if (value !== "") {
let num = Number(value);
value = isNaN(num) ? "" : num;
}
valueCol = (
<input
id={`${signal.name}_${field}`}
type="number"
value={value}
onChange={e => {
this.updateField(fieldSpec, e.target.value);
}}
/>
);
} else {
let value = this.props.signal[field];
valueCol = <span>{value}</span>;
}
return this.renderField(field, title, valueCol, signal);
}
renderStringField(fieldSpec, signal) {
const { field, title } = fieldSpec;
let valueCol;
if (this.props.isExpanded) {
valueCol = (
<input
id={`${signal.name}_${field}`}
type="text"
value={this.state.signalEdited[field] || ""}
onChange={e => {
this.updateField(fieldSpec, e.target.value);
}}
/>
);
} else {
valueCol = <span>{this.props.signal[field]}</span>;
}
toggleSignalPlot(e) {
const {signal, isPlotted} = this.props;
e.preventDefault();
this.props.onSignalPlotChange(!isPlotted, signal.uid);
return this.renderField(field, title, valueCol, signal);
}
renderOptionField(fieldSpec, signal) {
let valueCol;
const { field, title } = fieldSpec;
const { options, optionValues } = fieldSpec.options;
let valueOptions = swapKeysAndValues(optionValues);
if (this.props.isExpanded) {
const optionEles = options.map(opt => (
<option key={opt} value={optionValues[opt]}>
{opt}
</option>
));
valueCol = (
<select
id={`${signal.name}_${field}`}
defaultValue={this.state.signalEdited[field]}
onChange={e => {
this.updateField(fieldSpec, e.target.value === "true");
}}
>
{optionEles}
</select>
);
} else {
valueCol = <span>{valueOptions[this.props.signal[field]]}</span>;
}
render() {
const {signal} = this.props;
const expandedEntryClass = this.props.isExpanded ? 'is-expanded' : null;
const highlightedEntryClass = this.props.isHighlighted ? 'is-highlighted' : null;
const plottedButtonClass = this.props.isPlotted ? 'button' : 'button--alpha';
const plottedButtonText = this.props.isPlotted ? 'Hide Plot' : 'Show Plot';
return (
return this.renderField(field, title, valueCol, signal);
}
renderFieldNode(field, signal) {
if (field.type === "number") {
return this.renderNumberField(field, signal);
} else if (field.type === "option") {
return this.renderOptionField(field, signal);
} else if (field.type === "string") {
return this.renderStringField(field, signal);
}
}
toggleEditing(e) {
let { signalEdited } = this.state;
const { signal, isExpanded } = this.props;
const signalCopy = Object.assign(Object.create(signal), signal);
if (isExpanded) {
// Finished editing, save changes & reset intermediate
// signalEdited state.
Object.entries(signalEdited).forEach(([field, value]) => {
const fieldSpec = SignalLegendEntry.fieldSpecForName(field);
if (
fieldSpec &&
fieldSpec.type === "number" &&
isNaN(parseInt(value, 10))
) {
value = 0;
}
signalCopy[field] = value;
});
this.props.onSignalChange(signalCopy, signal);
} else {
signalEdited = signalCopy;
}
// Expand and enable signal editing
this.setState({
signalEdited
});
this.props.toggleExpandSignal(signal);
e.stopPropagation();
}
renderSignalForm(signal) {
return (
<div className="signals-legend-entry-form">
{SignalLegendEntry.fields.map(field => {
return (
<div className="signals-legend-entry-form-field" key={field.field}>
{this.renderFieldNode(field, signal)}
</div>
);
})}
<div className="signals-legend-entry-form-remove">
<button
className="button--tiny button--alpha"
onClick={() => this.props.onSignalRemove(signal)}
>
Remove Signal
</button>
</div>
</div>
);
}
toggleSignalPlot(e) {
const { signal, isPlotted } = this.props;
e.preventDefault();
this.props.onSignalPlotChange(!isPlotted, signal.uid);
}
render() {
const { signal } = this.props;
const expandedEntryClass = this.props.isExpanded ? "is-expanded" : null;
const highlightedEntryClass = this.props.isHighlighted
? "is-highlighted"
: null;
const plottedButtonClass = this.props.isPlotted
? "button"
: "button--alpha";
const plottedButtonText = this.props.isPlotted ? "Hide Plot" : "Show Plot";
return (
<div
className={cx(
"signals-legend-entry",
expandedEntryClass,
highlightedEntryClass
)}
onMouseEnter={() => this.props.onSignalHover(signal)}
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}
>
<div
className="signals-legend-entry-color"
style={{ backgroundColor: `rgb(${this.props.color}` }}
/>
<div className="signals-legend-entry-header">
<div
className={cx('signals-legend-entry', expandedEntryClass, highlightedEntryClass)}
onMouseEnter={() => this.props.onSignalHover(signal)}
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}>
<div className='signals-legend-entry-color'
style={{backgroundColor: `rgb(${this.props.color}`}}></div>
<div className="signals-legend-entry-header">
<div
className="signals-legend-entry-header-name"
onClick={this.toggleEditing}>
<strong>{signal.name}</strong>
</div>
<div
className="signals-legend-entry-header-action"
onClick={this.toggleSignalPlot}>
<button className={cx('button--tiny', plottedButtonClass)}>
{plottedButtonText}
</button>
</div>
</div>
<div className="signals-legend-entry-body">
{this.props.isExpanded ? this.renderSignalForm(signal) : null}
</div>
className="signals-legend-entry-header-name"
onClick={this.toggleEditing}
>
<strong>{signal.name}</strong>
</div>
);
}
<div
className="signals-legend-entry-header-action"
onClick={this.toggleSignalPlot}
>
<button className={cx("button--tiny", plottedButtonClass)}>
{plottedButtonText}
</button>
</div>
</div>
<div className="signals-legend-entry-body">
{this.props.isExpanded ? this.renderSignalForm(signal) : null}
</div>
</div>
);
}
}

View File

@ -1,22 +1,26 @@
import {getUrlParameter} from './utils/url';
import { getUrlParameter } from "./utils/url";
const ENV = process.env.NODE_ENV === "production" ? "prod" : "debug";
const ENV = process.env.NODE_ENV === 'production' ? 'prod' : 'debug';
const ENV_GITHUB_CLIENT_ID = {debug: 'f1e42d14f45491f9ca34',
prod: '4b43250e7499a97d62a5'}
const ENV_GITHUB_CLIENT_ID = {
debug: "f1e42d14f45491f9ca34",
prod: "4b43250e7499a97d62a5"
};
export const GITHUB_CLIENT_ID = ENV_GITHUB_CLIENT_ID[ENV];
const ENV_GITHUB_REDIRECT_URL = {debug: 'http://127.0.0.1:1235/callback',
prod: 'https://api.comma.ai/cabana/ghcallback'}
const ENV_GITHUB_REDIRECT_URL = {
debug: "http://127.0.0.1:1235/callback",
prod: "https://api.comma.ai/cabana/ghcallback"
};
export const GITHUB_REDIRECT_URL = ENV_GITHUB_REDIRECT_URL[ENV];
export const GITHUB_AUTH_TOKEN_KEY = 'gh_access_token';
export const OPENDBC_SOURCE_REPO = 'commaai/opendbc';
export const GITHUB_AUTH_TOKEN_KEY = "gh_access_token";
export const OPENDBC_SOURCE_REPO = "commaai/opendbc";
export const USE_UNLOGGER = (typeof window !== 'undefined' && getUrlParameter('unlogger') !== null);
export const UNLOGGER_HOST = 'http://localhost:8080/unlogger';
export const USE_UNLOGGER =
typeof window !== "undefined" && getUrlParameter("unlogger") !== null;
export const UNLOGGER_HOST = "http://localhost:8080/unlogger";
export const LOGENTRIES_TOKEN = '4bc98019-8277-4fe0-867c-ed21ea843cc5';
export const LOGENTRIES_TOKEN = "4bc98019-8277-4fe0-867c-ed21ea843cc5";
export const PART_SEGMENT_LENGTH = 3;
@ -24,5 +28,5 @@ export const CAN_GRAPH_MAX_POINTS = 10000;
export const STREAMING_WINDOW = 60;
export const COMMA_ACCESS_TOKEN_COOKIE = 'comma_access_token';
export const COMMA_OAUTH_REDIRECT_COOKIE = 'wiki_login_redirect';
export const COMMA_ACCESS_TOKEN_COOKIE = "comma_access_token";
export const COMMA_OAUTH_REDIRECT_COOKIE = "wiki_login_redirect";

View File

@ -1,49 +1,53 @@
import LogEntries from './LogEntries';
import {LOGENTRIES_TOKEN} from '../config';
import LogEntries from "./LogEntries";
import { LOGENTRIES_TOKEN } from "../config";
class CloudLog {
constructor() {
LogEntries.init({token: LOGENTRIES_TOKEN,
no_format: true,
catchall: false});
this.context = {};
constructor() {
LogEntries.init({
token: LOGENTRIES_TOKEN,
no_format: true,
catchall: false
});
this.context = {};
}
bind(obj) {
this.context.update(obj);
}
emit(message, level = "log") {
if (typeof global.__JEST__ !== "undefined") {
// Don't log in testing environment
return;
}
bind(obj) {
this.context.update(obj);
const entry = {
ctx: this.context,
created: new Date().getTime() / 1000,
msg: message,
src: "JSCloudLog"
};
if (level === "log") {
LogEntries.log(entry);
} else if (level === "warn") {
LogEntries.warn(entry);
} else if (level === "error") {
LogEntries.error(entry);
}
}
emit(message, level = 'log') {
if(typeof global.__JEST__ !== 'undefined') {
// Don't log in testing environment
return
}
log(message) {
this.emit(message);
}
const entry = {ctx: this.context,
created: new Date().getTime() / 1000,
msg: message,
src: 'JSCloudLog'};
warn(message) {
this.emit(message, "warn");
}
if(level === 'log') {
LogEntries.log(entry);
} else if(level === 'warn') {
LogEntries.warn(entry);
} else if(level === 'error') {
LogEntries.error(entry);
}
}
log(message) {
this.emit(message);
}
warn(message) {
this.emit(message, 'warn');
}
error(message) {
this.emit(message, 'error');
}
error(message) {
this.emit(message, "error");
}
}
export default (new CloudLog());
export default new CloudLog();

View File

@ -1,382 +1,388 @@
// Vendored from https://github.com/rapid7/le_js, which is broken with webpack.
/* eslint-disable */
if (typeof window === 'undefined') { // eslint-disable-line no-use-before-define
if (typeof window === "undefined") {
// eslint-disable-line no-use-before-define
var window = self;
}
var _indexOf = function (array, obj) {
for (var i = 0; i < array.length; i++) {
if (obj === array[i]) {
return i;
}
}
return -1;
var _indexOf = function(array, obj) {
for (var i = 0; i < array.length; i++) {
if (obj === array[i]) {
return i;
}
}
return -1;
};
// Obtain a browser-specific XHR object
var _getAjaxObject = function () {
if (typeof XDomainRequest !== "undefined") {
// We're using IE8/9
return new XDomainRequest();
}
return new XMLHttpRequest();
var _getAjaxObject = function() {
if (typeof XDomainRequest !== "undefined") {
// We're using IE8/9
return new XDomainRequest();
}
return new XMLHttpRequest();
};
/**
* A single log event stream.
* @constructor
* @param {Object} options
*/
* A single log event stream.
* @constructor
* @param {Object} options
*/
function LogStream(options) {
/**
* @const
* @type {string} */
var _traceCode = options.trace ? (Math.random() + Math.PI).toString(36).substring(2, 10) : null;
/** @type {string} */
var _pageInfo = options.page_info;
/** @type {string} */
var _token = options.token;
/** @type {boolean} */
var _print = options.print;
/** @type {boolean} */
var _noFormat = options.no_format;
/** @type {boolean} */
var _SSL = function() {
if (typeof XDomainRequest === "undefined") {
return options.ssl;
}
// If we're relying on XDomainRequest, we
// must adhere to the page's encryption scheme.
return window.location.protocol === "https:" ? true : false;
}();
/** @type {string} */
var _endpoint;
if (window.LEENDPOINT) {
_endpoint = window.LEENDPOINT;
} else if (_noFormat) {
_endpoint = "webhook.logentries.com/noformat";
}
else {
_endpoint = "js.logentries.com/v1";
}
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
/**
* @const
* @type {string} */
var _traceCode = options.trace
? (Math.random() + Math.PI).toString(36).substring(2, 10)
: null;
/** @type {string} */
var _pageInfo = options.page_info;
/** @type {string} */
var _token = options.token;
/** @type {boolean} */
var _print = options.print;
/** @type {boolean} */
var _noFormat = options.no_format;
/** @type {boolean} */
var _SSL = (function() {
if (typeof XDomainRequest === "undefined") {
return options.ssl;
}
// If we're relying on XDomainRequest, we
// must adhere to the page's encryption scheme.
return window.location.protocol === "https:" ? true : false;
})();
/** @type {string} */
var _endpoint;
if (window.LEENDPOINT) {
_endpoint = window.LEENDPOINT;
} else if (_noFormat) {
_endpoint = "webhook.logentries.com/noformat";
} else {
_endpoint = "js.logentries.com/v1";
}
_endpoint = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
/**
* Flag to prevent further invocations on network err
** @type {boolean} */
var _shouldCall = true;
/** @type {Array.<string>} */
var _backlog = [];
/** @type {boolean} */
var _active = false;
/** @type {boolean} */
var _sentPageInfo = false;
/**
* Flag to prevent further invocations on network err
** @type {boolean} */
var _shouldCall = true;
/** @type {Array.<string>} */
var _backlog = [];
/** @type {boolean} */
var _active = false;
/** @type {boolean} */
var _sentPageInfo = false;
var _apiCall = function(token, data) {
_active = true;
var _apiCall = function(token, data) {
_active = true;
var request = _getAjaxObject();
var request = _getAjaxObject();
if (_shouldCall) {
if (request.constructor === XMLHttpRequest) {
// Currently we don't support fine-grained error
// handling in older versions of IE
request.onreadystatechange = function() {
if (request.readyState === 4) {
// Handle any errors
if (request.status >= 400) {
console.error("Couldn't submit events.");
if (request.status === 410) {
// This API version has been phased out
console.warn("This version of le_js is no longer supported!");
}
} else {
if (request.status === 301) {
// Server issued a deprecation warning
console.warn("This version of le_js is deprecated! Consider upgrading.");
}
if (_backlog.length > 0) {
// Submit the next event in the backlog
_apiCall(token, _backlog.shift());
} else {
_active = false;
}
}
}
if (_shouldCall) {
if (request.constructor === XMLHttpRequest) {
// Currently we don't support fine-grained error
// handling in older versions of IE
request.onreadystatechange = function() {
if (request.readyState === 4) {
// Handle any errors
if (request.status >= 400) {
console.error("Couldn't submit events.");
if (request.status === 410) {
// This API version has been phased out
console.warn("This version of le_js is no longer supported!");
}
} else {
if (request.status === 301) {
// Server issued a deprecation warning
console.warn(
"This version of le_js is deprecated! Consider upgrading."
);
}
if (_backlog.length > 0) {
// Submit the next event in the backlog
_apiCall(token, _backlog.shift());
} else {
_active = false;
}
}
}
};
} else {
request.onload = function() {
if (_backlog.length > 0) {
// Submit the next event in the backlog
_apiCall(token, _backlog.shift());
} else {
_active = false;
}
};
}
};
} else {
request.onload = function() {
if (_backlog.length > 0) {
// Submit the next event in the backlog
_apiCall(token, _backlog.shift());
} else {
_active = false;
}
};
}
request.open("POST", _endpoint, true);
if (request.constructor === XMLHttpRequest) {
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
request.setRequestHeader("Content-type", "application/json");
}
request.open("POST", _endpoint, true);
if (request.constructor === XMLHttpRequest) {
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.setRequestHeader('Content-type', 'application/json');
}
if (request.overrideMimeType) {
request.overrideMimeType("text");
}
if (request.overrideMimeType) {
request.overrideMimeType('text');
}
request.send(data);
}
};
request.send(data);
}
};
var _getEvent = function() {
var raw = null;
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
throw new Error("No arguments!");
} else if (args.length === 1) {
raw = args[0];
} else {
// Handle a variadic overload,
// e.g. _rawLog("some text ", x, " ...", 1);
raw = args;
}
return raw;
};
var _getEvent = function() {
var raw = null;
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
throw new Error("No arguments!");
} else if (args.length === 1) {
raw = args[0];
} else {
// Handle a variadic overload,
// e.g. _rawLog("some text ", x, " ...", 1);
raw = args;
}
return raw;
};
var _agentInfo = function() {
var nav = window.navigator || { doNotTrack: undefined };
var screen = window.screen || {};
var location = window.location || {};
var _agentInfo = function() {
var nav = window.navigator || {doNotTrack: undefined};
var screen = window.screen || {};
var location = window.location || {};
return {
url: location.pathname,
referrer: document.referrer,
screen: {
width: screen.width,
height: screen.height
},
window: {
width: window.innerWidth,
height: window.innerHeight
},
browser: {
name: nav.appName,
version: nav.appVersion,
cookie_enabled: nav.cookieEnabled,
do_not_track: nav.doNotTrack
},
platform: nav.platform
};
};
return {
url: location.pathname,
referrer: document.referrer,
screen: {
width: screen.width,
height: screen.height
},
window: {
width: window.innerWidth,
height: window.innerHeight
},
browser: {
name: nav.appName,
version: nav.appVersion,
cookie_enabled: nav.cookieEnabled,
do_not_track: nav.doNotTrack
},
platform: nav.platform
};
};
// Single arg stops the compiler arity warning
var _rawLog = function(msg) {
var event = _getEvent.apply(this, arguments);
// Single arg stops the compiler arity warning
var _rawLog = function(msg) {
var event = _getEvent.apply(this, arguments);
var data = { event: event };
var data = {event: event};
// Add agent info if required
if (_pageInfo !== "never") {
if (!_sentPageInfo || _pageInfo === "per-entry") {
_sentPageInfo = true;
if (
typeof event.screen === "undefined" &&
typeof event.browser === "undefined"
)
_rawLog(_agentInfo())
.level("PAGE")
.send();
}
}
// Add agent info if required
if (_pageInfo !== 'never') {
if (!_sentPageInfo || _pageInfo === 'per-entry') {
_sentPageInfo = true;
if (typeof event.screen === "undefined" &&
typeof event.browser === "undefined")
_rawLog(_agentInfo()).level('PAGE').send();
}
}
if (_traceCode) {
data.trace = _traceCode;
}
if (_traceCode) {
data.trace = _traceCode;
}
return {
level: function(l) {
// Don't log PAGE events to console
// PAGE events are generated for the agentInfo function
if (_print && typeof console !== "undefined" && l !== "PAGE") {
var serialized = null;
if (typeof XDomainRequest !== "undefined") {
// We're using IE8/9
serialized = data.trace + " " + data.event;
}
try {
console[l.toLowerCase()].call(console, serialized || data);
} catch (ex) {
// IE compat fix
console.log(serialized || data);
}
}
data.level = l;
return {level: function(l) {
// Don't log PAGE events to console
// PAGE events are generated for the agentInfo function
if (_print && typeof console !== "undefined" && l !== 'PAGE') {
var serialized = null;
if (typeof XDomainRequest !== "undefined") {
// We're using IE8/9
serialized = data.trace + ' ' + data.event;
}
try {
console[l.toLowerCase()].call(console, (serialized || data));
} catch (ex) {
// IE compat fix
console.log((serialized || data));
}
}
data.level = l;
return {
send: function() {
var cache = [];
var serialized = JSON.stringify(data, function(key, value) {
if (typeof value === "undefined") {
return "undefined";
} else if (typeof value === "object" && value !== null) {
if (_indexOf(cache, value) !== -1) {
// We've seen this object before;
// return a placeholder instead to prevent
// cycles
return "<?>";
}
cache.push(value);
}
return value;
});
return {send: function() {
var cache = [];
var serialized = JSON.stringify(data, function(key, value) {
if (typeof value === "undefined") {
return "undefined";
} else if (typeof value === "object" && value !== null) {
if (_indexOf(cache, value) !== -1) {
// We've seen this object before;
// return a placeholder instead to prevent
// cycles
return "<?>";
}
cache.push(value);
}
return value;
});
if (_active) {
_backlog.push(serialized);
} else {
_apiCall(_token, serialized);
}
}};
}};
};
if (options.catchall) {
var oldHandler = window.onerror;
var newHandler = function(msg, url, line) {
_rawLog({error: msg, line: line, location: url}).level('ERROR').send();
if (oldHandler) {
return oldHandler(msg, url, line);
} else {
return false;
}
};
window.onerror = newHandler;
}
/** @expose */
this.log = _rawLog;
if (_active) {
_backlog.push(serialized);
} else {
_apiCall(_token, serialized);
}
}
};
}
};
};
if (options.catchall) {
var oldHandler = window.onerror;
var newHandler = function(msg, url, line) {
_rawLog({ error: msg, line: line, location: url })
.level("ERROR")
.send();
if (oldHandler) {
return oldHandler(msg, url, line);
} else {
return false;
}
};
window.onerror = newHandler;
}
/** @expose */
this.log = _rawLog;
}
/**
* A single log object
* @constructor
* @param {Object} options
*/
* A single log object
* @constructor
* @param {Object} options
*/
function Logger(options) {
var logger;
var logger;
// Default values
var dict = {
ssl: true,
catchall: false,
trace: true,
page_info: 'never',
print: false,
endpoint: null,
token: null
};
// Default values
var dict = {
ssl: true,
catchall: false,
trace: true,
page_info: "never",
print: false,
endpoint: null,
token: null
};
if (typeof options === "object")
for (var k in options)
dict[k] = options[k];
else
throw new Error("Invalid parameters for createLogStream()");
if (typeof options === "object") for (var k in options) dict[k] = options[k];
else throw new Error("Invalid parameters for createLogStream()");
if (dict.token === null) {
throw new Error("Token not present.");
} else {
logger = new LogStream(dict);
}
if (dict.token === null) {
throw new Error("Token not present.");
} else {
logger = new LogStream(dict);
}
var _log = function(msg) {
if (logger) {
return logger.log.apply(this, arguments);
} else
throw new Error("You must call LE.init(...) first.");
};
var _log = function(msg) {
if (logger) {
return logger.log.apply(this, arguments);
} else throw new Error("You must call LE.init(...) first.");
};
// The public interface
return {
log: function() {
_log.apply(this, arguments).level('LOG').send();
},
warn: function() {
_log.apply(this, arguments).level('WARN').send();
},
error: function() {
_log.apply(this, arguments).level('ERROR').send();
},
info: function() {
_log.apply(this, arguments).level('INFO').send();
}
};
// The public interface
return {
log: function() {
_log
.apply(this, arguments)
.level("LOG")
.send();
},
warn: function() {
_log
.apply(this, arguments)
.level("WARN")
.send();
},
error: function() {
_log
.apply(this, arguments)
.level("ERROR")
.send();
},
info: function() {
_log
.apply(this, arguments)
.level("INFO")
.send();
}
};
}
// Array of Logger elements
var loggers = {};
var _getLogger = function(name) {
if (!loggers.hasOwnProperty(name))
throw new Error("Invalid name for logStream");
if (!loggers.hasOwnProperty(name))
throw new Error("Invalid name for logStream");
return loggers[name];
return loggers[name];
};
var _createLogStream = function(options) {
if (typeof options.name !== "string")
throw new Error("Name not present.");
else if (loggers.hasOwnProperty(options.name))
throw new Error("A logger with that name already exists!");
loggers[options.name] = new Logger(options);
var _createLogStream = function(options) {
if (typeof options.name !== "string") throw new Error("Name not present.");
else if (loggers.hasOwnProperty(options.name))
throw new Error("A logger with that name already exists!");
loggers[options.name] = new Logger(options);
return true;
return true;
};
var _deprecatedInit = function(options) {
var dict = {
name : "default"
};
var dict = {
name: "default"
};
if (typeof options === "object")
for (var k in options)
dict[k] = options[k];
else if (typeof options === "string")
dict.token = options;
else
throw new Error("Invalid parameters for init()");
if (typeof options === "object") for (var k in options) dict[k] = options[k];
else if (typeof options === "string") dict.token = options;
else throw new Error("Invalid parameters for init()");
return _createLogStream(dict);
return _createLogStream(dict);
};
var _destroyLogStream = function(name) {
if (typeof name === 'undefined'){
name = 'default';
}
if (typeof name === "undefined") {
name = "default";
}
delete loggers[name];
delete loggers[name];
};
// The public interface
export default {
init: _deprecatedInit,
createLogStream: _createLogStream,
to: _getLogger,
destroy: _destroyLogStream,
log: function() {
for (var k in loggers)
loggers[k].log.apply(this, arguments);
},
warn: function() {
for (var k in loggers)
loggers[k].warn.apply(this, arguments);
},
error: function() {
for (var k in loggers)
loggers[k].error.apply(this, arguments);
},
info: function() {
for (var k in loggers)
loggers[k].info.apply(this, arguments);
}
init: _deprecatedInit,
createLogStream: _createLogStream,
to: _getLogger,
destroy: _destroyLogStream,
log: function() {
for (var k in loggers) loggers[k].log.apply(this, arguments);
},
warn: function() {
for (var k in loggers) loggers[k].warn.apply(this, arguments);
},
error: function() {
for (var k in loggers) loggers[k].error.apply(this, arguments);
},
info: function() {
for (var k in loggers) loggers[k].info.apply(this, arguments);
}
};

View File

@ -1,17 +1,18 @@
import Raven from 'raven-js';
import Raven from "raven-js";
function init() {
if(process.env.NODE_ENV === 'production') {
const opts = {};
if (process.env.NODE_ENV === "production") {
const opts = {};
if(typeof __webpack_hash__ !== 'undefined') {
opts['release'] = __webpack_hash__; // eslint-disable-line no-undef
}
Raven
.config('https://50006e5d91894f508dd288bbbf4585a6@sentry.io/185303', opts)
.install();
if (typeof __webpack_hash__ !== "undefined") {
opts["release"] = __webpack_hash__; // eslint-disable-line no-undef
}
Raven.config(
"https://50006e5d91894f508dd288bbbf4585a6@sentry.io/185303",
opts
).install();
}
}
export default {init};
export default { init };

View File

@ -8,9 +8,9 @@ const bitArray = {
* slice until the end of the array.
* @return {bitArray} The requested slice.
*/
bitSlice: function (a, bstart, bend) {
a = bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
return (bend === undefined) ? a : bitArray.clamp(a, bend-bstart);
bitSlice: function(a, bstart, bend) {
a = bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
return bend === undefined ? a : bitArray.clamp(a, bend - bstart);
},
/**
@ -23,15 +23,17 @@ const bitArray = {
extract: function(a, bstart, blength) {
// FIXME: this Math.floor is not necessary at all, but for some reason
// seems to suppress a bug in the Chromium JIT.
var x, sh = Math.floor((-bstart-blength) & 31);
if ((bstart + blength - 1 ^ bstart) & -32) {
var x,
sh = Math.floor((-bstart - blength) & 31);
if (((bstart + blength - 1) ^ bstart) & -32) {
// it crosses a boundary
x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
x =
(a[(bstart / 32) | 0] << (32 - sh)) ^ (a[(bstart / 32 + 1) | 0] >>> sh);
} else {
// within a single word
x = a[bstart/32|0] >>> sh;
x = a[(bstart / 32) | 0] >>> sh;
}
return x & ((1<<blength) - 1);
return x & ((1 << blength) - 1);
},
/**
@ -40,16 +42,22 @@ const bitArray = {
* @param {bitArray} a2 The second array.
* @return {bitArray} The concatenation of a1 and a2.
*/
concat: function (a1, a2) {
concat: function(a1, a2) {
if (a1.length === 0 || a2.length === 0) {
return a1.concat(a2);
}
var last = a1[a1.length-1], shift = bitArray.getPartial(last);
var last = a1[a1.length - 1],
shift = bitArray.getPartial(last);
if (shift === 32) {
return a1.concat(a2);
} else {
return bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
return bitArray._shiftRight(
a2,
shift,
last | 0,
a1.slice(0, a1.length - 1)
);
}
},
@ -58,11 +66,14 @@ const bitArray = {
* @param {bitArray} a The array.
* @return {Number} The length of a, in bits.
*/
bitLength: function (a) {
var l = a.length, x;
if (l === 0) { return 0; }
bitLength: function(a) {
var l = a.length,
x;
if (l === 0) {
return 0;
}
x = a[l - 1];
return (l-1) * 32 + bitArray.getPartial(x);
return (l - 1) * 32 + bitArray.getPartial(x);
},
/**
@ -71,13 +82,15 @@ const bitArray = {
* @param {Number} len The length to truncate to, in bits.
* @return {bitArray} A new array, truncated to len bits.
*/
clamp: function (a, len) {
if (a.length * 32 < len) { return a; }
clamp: function(a, len) {
if (a.length * 32 < len) {
return a;
}
a = a.slice(0, Math.ceil(len / 32));
var l = a.length;
len &= 31;
if (l > 0 && len) {
a[l-1] = bitArray.partial(len, a[l-1] & (0x80000000 >> (len-1)), 1);
a[l - 1] = bitArray.partial(len, a[l - 1] & (0x80000000 >> (len - 1)), 1);
}
return a;
},
@ -89,9 +102,11 @@ const bitArray = {
* @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
* @return {Number} The partial word.
*/
partial: function (len, x, _end) {
if (len === 32) { return x; }
return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
partial: function(len, x, _end) {
if (len === 32) {
return x;
}
return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
},
/**
@ -99,8 +114,8 @@ const bitArray = {
* @param {Number} x The partial word.
* @return {Number} The number of bits used by the partial word.
*/
getPartial: function (x) {
return Math.round(x/0x10000000000) || 32;
getPartial: function(x) {
return Math.round(x / 0x10000000000) || 32;
},
/**
@ -109,15 +124,16 @@ const bitArray = {
* @param {bitArray} b The second array.
* @return {boolean} true if a == b; false otherwise.
*/
equal: function (a, b) {
equal: function(a, b) {
if (bitArray.bitLength(a) !== bitArray.bitLength(b)) {
return false;
}
var x = 0, i;
for (i=0; i<a.length; i++) {
x |= a[i]^b[i];
var x = 0,
i;
for (i = 0; i < a.length; i++) {
x |= a[i] ^ b[i];
}
return (x === 0);
return x === 0;
},
/** Shift an array right.
@ -127,9 +143,13 @@ const bitArray = {
* @param {bitArray} [out=[]] An array to prepend to the output.
* @private
*/
_shiftRight: function (a, shift, carry, out) {
var i, last2=0, shift2;
if (out === undefined) { out = []; }
_shiftRight: function(a, shift, carry, out) {
var i,
last2 = 0,
shift2;
if (out === undefined) {
out = [];
}
for (; shift >= 32; shift -= 32) {
out.push(carry);
@ -139,21 +159,27 @@ const bitArray = {
return out.concat(a);
}
for (i=0; i<a.length; i++) {
out.push(carry | (a[i]>>>shift));
carry = a[i] << (32-shift);
for (i = 0; i < a.length; i++) {
out.push(carry | (a[i] >>> shift));
carry = a[i] << (32 - shift);
}
last2 = a.length ? a[a.length-1] : 0;
last2 = a.length ? a[a.length - 1] : 0;
shift2 = bitArray.getPartial(last2);
out.push(bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
out.push(
bitArray.partial(
(shift + shift2) & 31,
shift + shift2 > 32 ? carry : out.pop(),
1
)
);
return out;
},
/** xor a block of 4 words together.
* @private
*/
_xor4: function(x,y) {
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
_xor4: function(x, y) {
return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
},
/** byteswap a word array inplace.
@ -162,7 +188,9 @@ const bitArray = {
* @return {bitArray} byteswapped array
*/
byteswapM: function(a) {
var i, v, m = 0xff00;
var i,
v,
m = 0xff00;
for (i = 0; i < a.length; ++i) {
v = a[i];
a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
@ -173,18 +201,20 @@ const bitArray = {
export default {
fromBytes: function(bytes) {
var out = [], i, tmp=0;
for (i=0; i<bytes.length; i++) {
var out = [],
i,
tmp = 0;
for (i = 0; i < bytes.length; i++) {
tmp = (tmp << 8) | bytes[i];
if ((i&3) === 3) {
if ((i & 3) === 3) {
out.push(tmp);
tmp = 0;
}
}
if (i&3) {
out.push(bitArray.partial(8*(i&3), tmp));
if (i & 3) {
out.push(bitArray.partial(8 * (i & 3), tmp));
}
return out;
},
...bitArray
};
};

View File

@ -1,15 +1,15 @@
const Uint64BE = require('int64-buffer').Uint64BE
const Uint64BE = require("int64-buffer").Uint64BE;
export function formatForMsg(msg) {
return {bstart: 0, bend: 15}
return { bstart: 0, bend: 15 };
}
export function formatMsgDec(msg) {
const {bstart, bend} = formatForMsg(msg)
const uint = Uint64BE(msg[1])
var tt = "0"+uint.toString(2);
tt = tt.substring(0, tt.length - (63-bend))
tt = tt.substring(tt.length - (bend-bstart) - 1)
const { bstart, bend } = formatForMsg(msg);
const uint = Uint64BE(msg[1]);
var tt = "0" + uint.toString(2);
tt = tt.substring(0, tt.length - (63 - bend));
tt = tt.substring(tt.length - (bend - bstart) - 1);
return [msg[0], parseInt(tt, 2)];
}
@ -20,17 +20,17 @@ export function uint64BEToHex(int64) {
export function int64BufferToPrettyHexStr(buffer) {
const uint = Uint64BE(buffer);
let hex = uint.toString(16);
if(hex.length === 1) hex = '0' + hex;
if (hex.length === 1) hex = "0" + hex;
let hexParts = hex.match(/.{1,2}/g);
return hexParts.join(" ")
return hexParts.join(" ");
}
export function formatMsgHex(msg) {
const uint = Uint64BE(msg[1]);
let hex = uint.toString(16);
if(hex.length === 1) hex = '0' + hex;
let hexParts = hex.match(/.{1,2}/g);
const uint = Uint64BE(msg[1]);
let hex = uint.toString(16);
if (hex.length === 1) hex = "0" + hex;
let hexParts = hex.match(/.{1,2}/g);
return [msg[0], hexParts.join(" ")]
}
return [msg[0], hexParts.join(" ")];
}

View File

@ -1,11 +1,11 @@
export default class BoardUnit {
constructor(name) {
this.name = name;
this.attributes = {};
this.comment = null
}
constructor(name) {
this.name = name;
this.attributes = {};
this.comment = null;
}
text() {
return this.name;
}
}
text() {
return this.name;
}
}

View File

@ -421,12 +421,9 @@ export default class DBC {
messageId = parseInt(messageId, 10);
const msg = messages.get(messageId);
if (msg === undefined) {
warnings.push(`failed to parse signal comment on line ${i + 1} -- ${
line
}:
message id ${
messageId
} does not exist prior to this line`);
warnings.push(`failed to parse signal comment on line ${i +
1} -- ${line}:
message id ${messageId} does not exist prior to this line`);
continue;
}
const signal = msg.signals[signalName];

View File

@ -1,13 +1,17 @@
function findTimeIndex(entries, time) {
return entries.findIndex((e) => e.time >= time);
return entries.findIndex(e => e.time >= time);
}
function findRelativeTimeIndex(entries, relTime) {
return entries.findIndex((e) => e.relTime >= relTime);
return entries.findIndex(e => e.relTime >= relTime);
}
function findSegmentIndices(entries, [segmentTimeLow, segmentTimeHi], isRelative) {
/*
function findSegmentIndices(
entries,
[segmentTimeLow, segmentTimeHi],
isRelative
) {
/*
Finds pair of indices (inclusive, exclusive) within entries array
whose timestamps match segmentTimeLow and segmentTimeHi.
if isRelative === true, then the segment times
@ -16,15 +20,19 @@ function findSegmentIndices(entries, [segmentTimeLow, segmentTimeHi], isRelative
Returns `[segmentIdxLow, segmentIdxHigh]`
(inclusive, exclusive)
*/
let timeIndexFunc = (isRelative === true ? findRelativeTimeIndex : findTimeIndex);
let timeIndexFunc =
isRelative === true ? findRelativeTimeIndex : findTimeIndex;
const segmentIdxLow = timeIndexFunc(entries, segmentTimeLow);
const segmentIdxLow = timeIndexFunc(entries, segmentTimeLow);
const upperSegments = entries.slice(segmentIdxLow);
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
const segmentIdxHi = (upperSegmentIdxHi >= 0 ? upperSegmentIdxHi + segmentIdxLow + 1 : entries.length - 1)
const upperSegments = entries.slice(segmentIdxLow);
let upperSegmentIdxHi = timeIndexFunc(upperSegments, segmentTimeHi);
const segmentIdxHi =
upperSegmentIdxHi >= 0
? upperSegmentIdxHi + segmentIdxLow + 1
: entries.length - 1;
return [segmentIdxLow, Math.min(segmentIdxHi, entries.length - 1)]
return [segmentIdxLow, Math.min(segmentIdxHi, entries.length - 1)];
}
export default {findTimeIndex, findRelativeTimeIndex, findSegmentIndices};
export default { findTimeIndex, findRelativeTimeIndex, findSegmentIndices };

View File

@ -1,56 +1,63 @@
export default class Frame {
constructor({name,
id = 0,
size = 0,
transmitters = [],
extended = 0,
comment = null,
signals = {}}) {
Object.assign(this, {name,
id,
size,
transmitters,
extended,
comment,
signals})
constructor({
name,
id = 0,
size = 0,
transmitters = [],
extended = 0,
comment = null,
signals = {}
}) {
Object.assign(this, {
name,
id,
size,
transmitters,
extended,
comment,
signals
});
}
nextNewTransmitterName() {
let txNum = 1,
txName;
do {
txName = "NEW_TRANSMITTER_" + txNum;
txNum++;
} while (this.transmitters.indexOf(txName) !== -1);
return txName;
}
addTransmitter() {
const txName = this.nextNewTransmitterName();
this.transmitters.push(txName);
return txName;
}
header() {
return (
`BO_ ${this.id} ${this.name}: ${this.size} ` +
`${this.transmitters[0] || "XXX"}`
);
}
text() {
const signals = Object.values(this.signals)
.map(signal => " " + signal.text()) // indent
.join("\n");
if (signals.length > 0) {
return this.header() + "\n" + signals;
} else {
return this.header();
}
}
nextNewTransmitterName() {
let txNum = 1, txName;
do {
txName = 'NEW_TRANSMITTER_' + txNum;
txNum++;
} while(this.transmitters.indexOf(txName) !== -1);
copy() {
const copy = Object.assign(Object.create(this), this);
return txName;
}
addTransmitter() {
const txName = this.nextNewTransmitterName();
this.transmitters.push(txName);
return txName;
}
header() {
return `BO_ ${this.id} ${this.name}: ${this.size} ` +
`${this.transmitters[0] || 'XXX'}`;
}
text() {
const signals = Object.values(this.signals)
.map((signal) => " " + signal.text()) // indent
.join("\n");
if(signals.length > 0) {
return this.header() + "\n" + signals;
} else {
return this.header();
}
}
copy() {
const copy = Object.assign(Object.create(this), this);
return copy;
}
return copy;
}
}

View File

@ -1,141 +1,173 @@
import ArrayUtils from '../utils/array';
import {CAN_GRAPH_MAX_POINTS} from '../config';
import ArrayUtils from "../utils/array";
import { CAN_GRAPH_MAX_POINTS } from "../config";
function _calcGraphData(msg, signalUid, firstCanTime) {
if(!msg) return null;
if (!msg) return null;
const signal = Object.values(msg.frame.signals).find((s) => s.uid === signalUid);
if(!signal) {
console.warn('_calcGraphData: no signal', signalUid, msg)
return null;
}
let samples = [];
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
const signal = Object.values(msg.frame.signals).find(
s => s.uid === signalUid
);
if (!signal) {
console.warn("_calcGraphData: no signal", signalUid, msg);
return null;
}
let samples = [];
let skip = Math.floor(msg.entries.length / CAN_GRAPH_MAX_POINTS);
if(skip === 0){
samples = msg.entries;
} else {
for(let i = 0; i < msg.entries.length; i += skip) {
samples.push(msg.entries[i]);
}
// Always include last message entry, which faciliates graphData comparison
samples.push(msg.entries[msg.entries.length - 1]);
if (skip === 0) {
samples = msg.entries;
} else {
for (let i = 0; i < msg.entries.length; i += skip) {
samples.push(msg.entries[i]);
}
return samples.filter((e) => e.signals[signal.name] !== undefined)
.map((entry) => {
return {x: entry.time,
relTime: entry.relTime,
y: entry.signals[signal.name],
unit: signal.unit,
color: `rgba(${signal.colors.join(",")}, 0.5)`,
signalName: signal.name,
signalUid}
// Always include last message entry, which faciliates graphData comparison
samples.push(msg.entries[msg.entries.length - 1]);
}
return samples
.filter(e => e.signals[signal.name] !== undefined)
.map(entry => {
return {
x: entry.time,
relTime: entry.relTime,
y: entry.signals[signal.name],
unit: signal.unit,
color: `rgba(${signal.colors.join(",")}, 0.5)`,
signalName: signal.name,
signalUid
};
});
}
function appendNewGraphData(plottedSignals, graphData, messages, firstCanTime) {
const messagesPerPlot = plottedSignals.map((plottedMessages) =>
plottedMessages.reduce((messages,
{messageId, signalUid}) => {
messages.push(messageId);
return messages;
}, [])
const messagesPerPlot = plottedSignals.map(plottedMessages =>
plottedMessages.reduce((messages, { messageId, signalUid }) => {
messages.push(messageId);
return messages;
}, [])
);
const extendedPlots = messagesPerPlot
.map((plottedMessageIds, index) => {
return { plottedMessageIds, index };
}) // preserve index so we can look up graphData
.filter(({ plottedMessageIds, index }) => {
if (index < graphData.length) {
let maxGraphTime = 0;
const { series } = graphData[index];
if (series.length > 0) {
maxGraphTime = series[graphData[index].series.length - 1].relTime;
}
return plottedMessageIds.some(
messageId =>
(messages[messageId].entries.length > 0 && series.length === 0) ||
messages[messageId].entries.some(e => e.relTime > maxGraphTime)
);
} else {
return false;
}
})
.map(({ plottedMessageIds, index }) => {
plottedMessageIds = plottedMessageIds.reduce((arr, messageId) => {
if (arr.indexOf(messageId) === -1) {
arr.push(messageId);
}
return arr;
}, []);
return { plottedMessageIds, index };
});
extendedPlots.forEach(({ plottedMessageIds, index }) => {
const signalUidsByMessageId = plottedSignals[index].reduce(
(obj, { messageId, signalUid }) => {
if (!obj[messageId]) {
obj[messageId] = [];
}
obj[messageId].push(signalUid);
return obj;
},
{}
);
const { series } = graphData[index];
const graphDataMaxMessageTimes = plottedMessageIds.reduce(
(obj, messageId) => {
const signalUids = signalUidsByMessageId[messageId];
const maxIndex = ArrayUtils.findIndexRight(series, entry => {
return signalUids.indexOf(entry.signalUid) !== -1;
});
if (maxIndex) {
obj[messageId] = series[maxIndex].relTime;
} else if (series.length > 0) {
obj[messageId] = series[series.length - 1].relTime;
} else {
// Graph data is empty
obj[messageId] = -1;
}
return obj;
},
{}
);
let newGraphData = [];
plottedMessageIds
.map(messageId => {
return { messageId, entries: messages[messageId].entries };
})
.filter(
(
{ messageId, entries } // Filter to only messages with stale graphData
) =>
entries[entries.length - 1].relTime >
graphDataMaxMessageTimes[messageId]
)
.forEach(({ messageId, entries }) => {
// Compute and append new graphData
let firstNewEntryIdx = entries.findIndex(
entry => entry.relTime > graphDataMaxMessageTimes[messageId]
);
const extendedPlots = messagesPerPlot
.map((plottedMessageIds, index) => {return {plottedMessageIds, index}}) // preserve index so we can look up graphData
.filter(({plottedMessageIds, index}) => {
if(index < graphData.length) {
let maxGraphTime = 0;
const { series } = graphData[index];
if(series.length > 0) {
maxGraphTime = series[graphData[index].series.length - 1].relTime;
}
const newEntries = entries.slice(firstNewEntryIdx);
signalUidsByMessageId[messageId].forEach(signalUid => {
const signalGraphData = _calcGraphData(
{
...messages[messageId],
entries: newEntries
},
signalUid,
firstCanTime
);
return plottedMessageIds.some((messageId) =>
(messages[messageId].entries.length > 0 && series.length === 0)
||
messages[messageId].entries.some((e) => e.relTime > maxGraphTime));
} else {
return false;
}
}).map(({plottedMessageIds, index}) => {
plottedMessageIds = plottedMessageIds.reduce((arr, messageId) => {
if(arr.indexOf(messageId) === -1) {
arr.push(messageId);
}
return arr;
}, []);
return {plottedMessageIds, index};
});
extendedPlots.forEach(({plottedMessageIds, index}) => {
const signalUidsByMessageId = plottedSignals[index].reduce((obj, {messageId, signalUid}) => {
if(!obj[messageId]) {
obj[messageId] = []
}
obj[messageId].push(signalUid);
return obj;
}, {});
const { series } = graphData[index];
const graphDataMaxMessageTimes = plottedMessageIds.reduce((obj, messageId) => {
const signalUids = signalUidsByMessageId[messageId];
const maxIndex = ArrayUtils.findIndexRight(series, (entry) => {
return signalUids.indexOf(entry.signalUid) !== -1
});
if(maxIndex) {
obj[messageId] = series[maxIndex].relTime;
} else if(series.length > 0) {
obj[messageId] = series[series.length - 1].relTime;
} else {
// Graph data is empty
obj[messageId] = -1;
}
return obj;
}, {});
let newGraphData = [];
plottedMessageIds.map((messageId) => {
return { messageId, entries: messages[messageId].entries }
}).filter(({messageId, entries}) => // Filter to only messages with stale graphData
entries[entries.length - 1].relTime > graphDataMaxMessageTimes[messageId])
.forEach(({messageId, entries}) => { // Compute and append new graphData
let firstNewEntryIdx = entries.findIndex((entry) =>
entry.relTime > graphDataMaxMessageTimes[messageId]);
const newEntries = entries.slice(firstNewEntryIdx);
signalUidsByMessageId[messageId].forEach((signalUid) => {
const signalGraphData = _calcGraphData({...messages[messageId],
entries: newEntries},
signalUid,
firstCanTime);
newGraphData = newGraphData.concat(signalGraphData);
});
newGraphData = newGraphData.concat(signalGraphData);
});
});
const messageIdOutOfBounds = (
series.length > 0
&& plottedMessageIds.find((messageId) =>
messages[messageId].entries.length > 0
&& series[0].relTime < messages[messageId].entries[0].relTime));
graphData[index] = {
series: graphData[index].series.concat(newGraphData),
updated: Date.now()
};
const messageIdOutOfBounds =
series.length > 0 &&
plottedMessageIds.find(
messageId =>
messages[messageId].entries.length > 0 &&
series[0].relTime < messages[messageId].entries[0].relTime
);
graphData[index] = {
series: graphData[index].series.concat(newGraphData),
updated: Date.now()
};
if(messageIdOutOfBounds) {
const graphDataLowerBound = graphData[index].series.findIndex(
(e) => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime);
if (messageIdOutOfBounds) {
const graphDataLowerBound = graphData[index].series.findIndex(
e => e.relTime > messages[messageIdOutOfBounds].entries[0].relTime
);
if(graphDataLowerBound) {
graphData[index].series = graphData[index].series.slice(graphDataLowerBound);
}
}
});
if (graphDataLowerBound) {
graphData[index].series = graphData[index].series.slice(
graphDataLowerBound
);
}
}
});
return [...graphData];
return [...graphData];
}
export default {_calcGraphData, appendNewGraphData};
export default { _calcGraphData, appendNewGraphData };

View File

@ -1,51 +1,67 @@
import React from 'react';
import {StyleSheet, css} from 'aphrodite/no-important';
import React from "react";
import { StyleSheet, css } from "aphrodite/no-important";
import * as ObjectUtils from '../utils/object';
import * as ObjectUtils from "../utils/object";
function createImageComponent(source, alt, styles) {
if(styles === undefined) {
styles = []
} else if(!Array.isArray(styles)) {
styles = [styles];
if (styles === undefined) {
styles = [];
} else if (!Array.isArray(styles)) {
styles = [styles];
}
return props => {
let localStyles = styles.slice();
if (Array.isArray(props.styles)) {
localStyles = localStyles.concat(props.styles);
// filter 'styles' from props, which is passed via spread to <img> tag.
props = ObjectUtils.fromArray(
Object.entries(props).filter(([k, v]) => k !== "styles")
);
}
return (props) => {
let localStyles = styles.slice();
if(Array.isArray(props.styles)) {
localStyles = localStyles.concat(props.styles);
// filter 'styles' from props, which is passed via spread to <img> tag.
props = ObjectUtils.fromArray(Object.entries(props).filter(([k,v]) => k !== 'styles'));
}
return <img src={source} className={css(...localStyles)} alt={alt} {...props} />
};
return (
<img src={source} className={css(...localStyles)} alt={alt} {...props} />
);
};
}
const Styles = StyleSheet.create({
materialIcon: {
width: 24,
height: 24,
},
pointer: {
cursor: 'pointer'
}
materialIcon: {
width: 24,
height: 24
},
pointer: {
cursor: "pointer"
}
});
const leftArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
'Left arrow',
Styles.materialIcon);
const rightArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_right_black_24dp.png",
'Right arrow',
Styles.materialIcon);
const leftArrow = createImageComponent(
process.env.PUBLIC_URL + "/img/ic_arrow_left_black_24dp.png",
"Left arrow",
Styles.materialIcon
);
const rightArrow = createImageComponent(
process.env.PUBLIC_URL + "/img/ic_arrow_right_black_24dp.png",
"Right arrow",
Styles.materialIcon
);
const downArrow = createImageComponent(process.env.PUBLIC_URL + "/img/ic_arrow_drop_down_black_24dp.png",
'Down arrow',
Styles.materialIcon)
const downArrow = createImageComponent(
process.env.PUBLIC_URL + "/img/ic_arrow_drop_down_black_24dp.png",
"Down arrow",
Styles.materialIcon
);
const clear = createImageComponent(process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
'Clear',
[Styles.materialIcon, Styles.pointer]);
const clear = createImageComponent(
process.env.PUBLIC_URL + "/img/ic_clear_black_24dp.png",
"Clear",
[Styles.materialIcon, Styles.pointer]
);
const panda = createImageComponent(process.env.PUBLIC_URL + "/img/panda.png", 'Panda', []);
export default {rightArrow, leftArrow, downArrow, clear, panda};
const panda = createImageComponent(
process.env.PUBLIC_URL + "/img/panda.png",
"Panda",
[]
);
export default { rightArrow, leftArrow, downArrow, clear, panda };

View File

@ -1,16 +1,16 @@
import {StyleSheet} from 'aphrodite/no-important';
import { StyleSheet } from "aphrodite/no-important";
export default StyleSheet.create({
tab: {
display: 'inline',
display: "inline",
marginRight: 20,
cursor: 'pointer'
cursor: "pointer"
},
selectedTab: {
borderBottom: '2px solid #000',
fontWeight: 'bold'
borderBottom: "2px solid #000",
fontWeight: "bold"
},
tabContent: {
paddingTop: 20
}
});
});

View File

@ -1,22 +1,22 @@
import {StyleSheet} from 'aphrodite/no-important';
import { StyleSheet } from "aphrodite/no-important";
export default StyleSheet.create({
root: {
flexDirection: 'row'
},
button: {
cursor: 'pointer',
backgroundColor: 'RGB(63, 135, 255)',
borderRadius: 5,
minWidth: 80,
paddingLeft: 20,
paddingRight: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
':hover': {
backgroundColor: 'RGBA(63, 135, 255, 0.5)'
}
root: {
flexDirection: "row"
},
button: {
cursor: "pointer",
backgroundColor: "RGB(63, 135, 255)",
borderRadius: 5,
minWidth: 80,
paddingLeft: 20,
paddingRight: 20,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "white",
":hover": {
backgroundColor: "RGBA(63, 135, 255, 0.5)"
}
});
}
});

View File

@ -1,13 +1,15 @@
function elementWiseEquals(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((ele, idx) => arr2[idx] === ele);
return (
arr1.length === arr2.length && arr1.every((ele, idx) => arr2[idx] === ele)
);
}
function findIndexRight(arr, condition) {
for(let i = arr.length - 1; i >= 0; i--) {
if(condition(arr[i])) {
return i;
}
for (let i = arr.length - 1; i >= 0; i--) {
if (condition(arr[i])) {
return i;
}
}
}
export default {elementWiseEquals, findIndexRight};
export default { elementWiseEquals, findIndexRight };

View File

@ -1,15 +1,17 @@
export function shade([r, g, b], opacity) {
/*
/*
@param {Array} rgb
@param {Number} opacity -1.0 -> +1.0
negative opacity will darken image
returns new RGB array
*/
const t = (opacity < 0) ? 0 : 255;
const opacityPos = Math.abs(opacity);
const t = opacity < 0 ? 0 : 255;
const opacityPos = Math.abs(opacity);
return [Math.round((t - r) * opacityPos) + r,
Math.round((t - g) * opacityPos) + g,
Math.round((t - b) * opacityPos) + b];
}
return [
Math.round((t - r) * opacityPos) + r,
Math.round((t - g) * opacityPos) + g,
Math.round((t - b) * opacityPos) + b
];
}

View File

@ -4,19 +4,17 @@
// Useful for component testing
import { css } from 'aphrodite';
import classNames from 'classnames';
import { css } from "aphrodite";
import classNames from "classnames";
export default function() {
const styles = [];
const styles = [];
const classes = [];
[].forEach.call(arguments, it => {
if (it && it._definition && it._name)
styles.push(it);
else
classes.push(it);
if (it && it._definition && it._name) styles.push(it);
else classes.push(it);
});
return classNames.apply(null, [ ...classes, css(styles) ]);
return classNames.apply(null, [...classes, css(styles)]);
}

View File

@ -1,145 +1,184 @@
require('core-js/fn/array/from');
require("core-js/fn/array/from");
function findMaxByteStateChangeCount(messages) {
return Object.values(messages).map((m) => m.byteStateChangeCounts)
.reduce((counts, countArr) => counts.concat(countArr), []) // flatten arrays
.reduce((count1, count2) => count1 > count2 ? count1 : count2, 0); // find max
return Object.values(messages)
.map(m => m.byteStateChangeCounts)
.reduce((counts, countArr) => counts.concat(countArr), []) // flatten arrays
.reduce((count1, count2) => (count1 > count2 ? count1 : count2), 0); // find max
}
function addCanMessage([address, busTime, data, source], dbc, canStartTime, messages, prevMsgEntries, byteStateChangeCountsByMessage) {
var id = source + ":" + address.toString(16);
function addCanMessage(
[address, busTime, data, source],
dbc,
canStartTime,
messages,
prevMsgEntries,
byteStateChangeCountsByMessage
) {
var id = source + ":" + address.toString(16);
if (messages[id] === undefined) messages[id] = createMessageSpec(dbc, address, id, source);
if (messages[id] === undefined)
messages[id] = createMessageSpec(dbc, address, id, source);
const prevMsgEntry = messages[id].entries.length > 0 ?
messages[id].entries[messages[id].entries.length - 1]
:
(prevMsgEntries[id] || null);
const prevMsgEntry =
messages[id].entries.length > 0
? messages[id].entries[messages[id].entries.length - 1]
: prevMsgEntries[id] || null;
if(byteStateChangeCountsByMessage[id] && messages[id].byteStateChangeCounts.every((c) => c === 0)) {
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id]
}
if (
byteStateChangeCountsByMessage[id] &&
messages[id].byteStateChangeCounts.every(c => c === 0)
) {
messages[id].byteStateChangeCounts = byteStateChangeCountsByMessage[id];
}
const {msgEntry,
byteStateChangeCounts} = parseMessage(dbc,
busTime,
address,
data,
canStartTime,
prevMsgEntry);
const { msgEntry, byteStateChangeCounts } = parseMessage(
dbc,
busTime,
address,
data,
canStartTime,
prevMsgEntry
);
messages[id].byteStateChangeCounts = byteStateChangeCounts.map((count, idx) =>
messages[id].byteStateChangeCounts[idx] + count
);
messages[id].byteStateChangeCounts = byteStateChangeCounts.map(
(count, idx) => messages[id].byteStateChangeCounts[idx] + count
);
messages[id].entries.push(msgEntry);
messages[id].entries.push(msgEntry);
return msgEntry;
return msgEntry;
}
function createMessageSpec(dbc, address, id, bus) {
const frame = dbc.messages.get(address);
const size = frame ? frame.size : 8;
const frame = dbc.messages.get(address);
const size = frame ? frame.size : 8;
return {
address: address,
id: id,
bus: bus,
entries: [],
frame: frame,
byteColors: Array(size).fill(0),
byteStateChangeCounts: Array(size).fill(0)
};
return {
address: address,
id: id,
bus: bus,
entries: [],
frame: frame,
byteColors: Array(size).fill(0),
byteStateChangeCounts: Array(size).fill(0)
};
}
function determineByteStateChangeTimes(hexData, time, msgSize, lastParsedMessage) {
const byteStateChangeCounts = Array(msgSize).fill(0);
let byteStateChangeTimes;
function determineByteStateChangeTimes(
hexData,
time,
msgSize,
lastParsedMessage
) {
const byteStateChangeCounts = Array(msgSize).fill(0);
let byteStateChangeTimes;
if(!lastParsedMessage) {
byteStateChangeTimes = Array(msgSize).fill(time);
} else {
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
if (!lastParsedMessage) {
byteStateChangeTimes = Array(msgSize).fill(time);
} else {
byteStateChangeTimes = Array.from(lastParsedMessage.byteStateChangeTimes);
for(let i = 0; i < byteStateChangeTimes.length; i++) {
const currentData = hexData.substr(i * 2, 2),
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
for (let i = 0; i < byteStateChangeTimes.length; i++) {
const currentData = hexData.substr(i * 2, 2),
prevData = lastParsedMessage.hexData.substr(i * 2, 2);
if(currentData !== prevData) {
byteStateChangeTimes[i] = time;
byteStateChangeCounts[i] = 1;
}
if (currentData !== prevData) {
byteStateChangeTimes[i] = time;
byteStateChangeCounts[i] = 1;
}
}
}
return {byteStateChangeTimes, byteStateChangeCounts};
return { byteStateChangeTimes, byteStateChangeCounts };
}
function createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes) {
return {
signals: dbc.getSignalValues(address, data),
time,
relTime,
hexData: Buffer.from(data).toString('hex'),
byteStateChangeTimes,
updated: Date.now(),
};
function createMessageEntry(
dbc,
address,
time,
relTime,
data,
byteStateChangeTimes
) {
return {
signals: dbc.getSignalValues(address, data),
time,
relTime,
hexData: Buffer.from(data).toString("hex"),
byteStateChangeTimes,
updated: Date.now()
};
}
function parseMessage(dbc, time, address, data, timeStart, lastParsedMessage) {
let hexData;
if(typeof data === 'string') {
hexData = data;
data = Buffer.from(data, 'hex');
} else {
hexData = Buffer.from(data).toString('hex');
}
const msgSpec = dbc.messages.get(address);
const msgSize = msgSpec ? msgSpec.size : 8;
const relTime = time - timeStart;
let hexData;
if (typeof data === "string") {
hexData = data;
data = Buffer.from(data, "hex");
} else {
hexData = Buffer.from(data).toString("hex");
}
const msgSpec = dbc.messages.get(address);
const msgSize = msgSpec ? msgSpec.size : 8;
const relTime = time - timeStart;
const {byteStateChangeTimes,
byteStateChangeCounts} = determineByteStateChangeTimes(hexData,
relTime,
msgSize,
lastParsedMessage);
const msgEntry = createMessageEntry(dbc, address, time, relTime, data, byteStateChangeTimes);
const {
byteStateChangeTimes,
byteStateChangeCounts
} = determineByteStateChangeTimes(
hexData,
relTime,
msgSize,
lastParsedMessage
);
const msgEntry = createMessageEntry(
dbc,
address,
time,
relTime,
data,
byteStateChangeTimes
);
return {msgEntry, byteStateChangeCounts};
return { msgEntry, byteStateChangeCounts };
}
const BIG_ENDIAN_START_BITS = [];
for(let i = 0; i < 64; i += 8) {
for(let j = 7; j > -1; j--) {
BIG_ENDIAN_START_BITS.push(i + j);
}
for (let i = 0; i < 64; i += 8) {
for (let j = 7; j > -1; j--) {
BIG_ENDIAN_START_BITS.push(i + j);
}
}
function bigEndianBitIndex(matrixBitIndex) {
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
return BIG_ENDIAN_START_BITS.indexOf(matrixBitIndex);
}
function matrixBitNumber(bigEndianIndex) {
return BIG_ENDIAN_START_BITS[bigEndianIndex];
return BIG_ENDIAN_START_BITS[bigEndianIndex];
}
function setMessageByteColors(message, maxByteStateChangeCount) {
message.byteColors = message.byteStateChangeCounts.map((count) =>
isNaN(count) ? 0 : Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
).map((red) =>
'rgb(' + Math.round(red) + ',0,0)'
);
message.byteColors = message.byteStateChangeCounts
.map(
count =>
isNaN(count)
? 0
: Math.min(255, 75 + 180 * (count / maxByteStateChangeCount))
)
.map(red => "rgb(" + Math.round(red) + ",0,0)");
return message;
}
export default {bigEndianBitIndex,
addCanMessage,
createMessageSpec,
matrixBitNumber,
parseMessage,
findMaxByteStateChangeCount,
setMessageByteColors,
createMessageEntry};
export default {
bigEndianBitIndex,
addCanMessage,
createMessageSpec,
matrixBitNumber,
parseMessage,
findMaxByteStateChangeCount,
setMessageByteColors,
createMessageEntry
};

View File

@ -1,14 +1,15 @@
export default function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}

View File

@ -1,58 +1,67 @@
const binTimeIntervals = [
{seconds: 1,
title: 'second'},
{seconds: 60,
title: 'minute'},
{seconds: 300,
title: '5 minutes'},
{seconds: 3600,
title: 'hour'}
{
seconds: 1,
title: "second"
},
{
seconds: 60,
title: "minute"
},
{
seconds: 300,
title: "5 minutes"
},
{
seconds: 3600,
title: "hour"
}
];
function prettyBinDuration(samplesDurationSeconds, maxBinCount = 100) {
for(let i = 0; i < binTimeIntervals.length; i++) {
const interval = binTimeIntervals[i];
if(samplesDurationSeconds / interval.seconds <= maxBinCount) {
return interval;
}
for (let i = 0; i < binTimeIntervals.length; i++) {
const interval = binTimeIntervals[i];
if (samplesDurationSeconds / interval.seconds <= maxBinCount) {
return interval;
}
}
// maxBinCount is exceeded.
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
// Just going to use hours.
// maxBinCount is exceeded.
// This means sampleDurationSeconds > 100 hours with default args. Quite a long trip.
// Just going to use hours.
return binTimeIntervals[3];
return binTimeIntervals[3];
}
export function binMessages(messageEntries, segmentIndices) {
let startIdx = 0, endIdx = messageEntries.length - 1;
if(segmentIndices && segmentIndices.length === 2) {
[startIdx, endIdx] = segmentIndices;
}
const first = messageEntries[startIdx], last = messageEntries[endIdx];
const binDuration = prettyBinDuration(last.time - first.time);
let startIdx = 0,
endIdx = messageEntries.length - 1;
if (segmentIndices && segmentIndices.length === 2) {
[startIdx, endIdx] = segmentIndices;
}
const first = messageEntries[startIdx],
last = messageEntries[endIdx];
const binDuration = prettyBinDuration(last.time - first.time);
const bins = [];
const offset = first.time - first.relTime;
const bins = [];
const offset = first.time - first.relTime;
for(let t = first.time; t < last.time; t += binDuration.seconds) {
const binCutoffTime = t + binDuration.seconds;
const previousBinEnd = startIdx;
for (let t = first.time; t < last.time; t += binDuration.seconds) {
const binCutoffTime = t + binDuration.seconds;
const previousBinEnd = startIdx;
let entry = {time: -1}
while(entry !== undefined && entry.time < binCutoffTime) {
entry = messageEntries[startIdx];
++startIdx;
}
bins.push({
count: startIdx - previousBinEnd,
startTime: t,
endTime: binCutoffTime,
relStartTime: t - offset
});
let entry = { time: -1 };
while (entry !== undefined && entry.time < binCutoffTime) {
entry = messageEntries[startIdx];
++startIdx;
}
return {bins: bins, binDuration}
bins.push({
count: startIdx - previousBinEnd,
startTime: t,
endTime: binCutoffTime,
relStartTime: t - offset
});
}
return { bins: bins, binDuration };
}

View File

@ -2,130 +2,133 @@
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
const NumpyLoader = (function NumpyLoader() {
function asciiDecode(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
function asciiDecode(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function readUint16LE(buffer) {
var view = new DataView(buffer);
var val = view.getUint8(0);
val |= view.getUint8(1) << 8;
return val;
}
function fromArrayBuffer(buf) {
// Check the magic number
var magic = asciiDecode(buf.slice(0, 6));
if (magic !== "\x93NUMPY") {
throw new Error("Bad magic number");
}
function readUint16LE(buffer) {
var view = new DataView(buffer);
var val = view.getUint8(0);
val |= view.getUint8(1) << 8;
return val;
}
var version = new Uint8Array(buf.slice(6, 8)),
headerLength = readUint16LE(buf.slice(8, 10)),
headerStr = asciiDecode(buf.slice(10, 10 + headerLength));
const offsetBytes = 10 + headerLength;
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
function fromArrayBuffer(buf) {
// Check the magic number
var magic = asciiDecode(buf.slice(0,6));
if (magic !== "\x93NUMPY") {
throw new Error("Bad magic number");
}
// Hacky conversion of dict literal string to JS Object
const info = JSON.parse(
headerStr
.toLowerCase()
.replace("(", "[")
.replace("),", "]")
.replace(/'/g, '"')
.replace(",]", "]")
);
var version = new Uint8Array(buf.slice(6,8)),
headerLength = readUint16LE(buf.slice(8,10)),
headerStr = asciiDecode(buf.slice(10, 10+headerLength));
const offsetBytes = 10 + headerLength;
//rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
// Hacky conversion of dict literal string to JS Object
const info = JSON.parse(headerStr.toLowerCase()
.replace('(','[')
.replace('),',']')
.replace(/'/g, '"')
.replace(',]', ']'));
// Intepret the bytes according to the specified dtype
var data;
if (info.descr === "|u1") {
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|i1") {
data = new Int8Array(buf, offsetBytes);
} else if (info.descr === "<u2") {
data = new Uint16Array(buf, offsetBytes);
} else if (info.descr === "<i2") {
data = new Int16Array(buf, offsetBytes);
} else if (info.descr === "<u4") {
data = new Uint32Array(buf, offsetBytes);
} else if (info.descr === "<i4") {
data = new Int32Array(buf, offsetBytes);
} else if (info.descr === "<f4") {
data = new Float32Array(buf, offsetBytes);
} else if (info.descr === "<f8") {
data = new Float64Array(buf, offsetBytes);
} else if (info.descr === "<u8") {
// 8 byte uint64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "<i8") {
// 8 byte int64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s5") {
// 5 byte string
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s8") {
// 8 byte strings
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '|s15') {
// 15 byte strings?
data = new Uint8Array(buf, offsetBytes);
} else {
throw new Error('unknown numeric dtype '+info.descr)
}
return {
shape: info.shape,
fortran_order: info.fortran_order,
data: data
};
}
function open(file, callback) {
var reader = new FileReader();
reader.onload = function() {
// the file contents have been read as an array buffer
var buf = reader.result;
var ndarray = fromArrayBuffer(buf);
callback(ndarray);
};
reader.readAsArrayBuffer(file);
}
function promise(url) {
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
var buf = req.response; // not responseText
var ndarray = fromArrayBuffer(buf);
resolve(ndarray);
} else if (req.status == 404) {
console.log('yup')
reject({is404: true})
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.open('GET', url, true);
req.responseType = "arraybuffer";
req.send(null);
});
// Intepret the bytes according to the specified dtype
var data;
if (info.descr === "|u1") {
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|i1") {
data = new Int8Array(buf, offsetBytes);
} else if (info.descr === "<u2") {
data = new Uint16Array(buf, offsetBytes);
} else if (info.descr === "<i2") {
data = new Int16Array(buf, offsetBytes);
} else if (info.descr === "<u4") {
data = new Uint32Array(buf, offsetBytes);
} else if (info.descr === "<i4") {
data = new Int32Array(buf, offsetBytes);
} else if (info.descr === "<f4") {
data = new Float32Array(buf, offsetBytes);
} else if (info.descr === "<f8") {
data = new Float64Array(buf, offsetBytes);
} else if (info.descr === "<u8") {
// 8 byte uint64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "<i8") {
// 8 byte int64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s5") {
// 5 byte string
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s8") {
// 8 byte strings
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === "|s15") {
// 15 byte strings?
data = new Uint8Array(buf, offsetBytes);
} else {
throw new Error("unknown numeric dtype " + info.descr);
}
return {
open: open,
promise: promise,
fromArrayBuffer: fromArrayBuffer,
shape: info.shape,
fortran_order: info.fortran_order,
data: data
};
}
function open(file, callback) {
var reader = new FileReader();
reader.onload = function() {
// the file contents have been read as an array buffer
var buf = reader.result;
var ndarray = fromArrayBuffer(buf);
callback(ndarray);
};
reader.readAsArrayBuffer(file);
}
function promise(url) {
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
var buf = req.response; // not responseText
var ndarray = fromArrayBuffer(buf);
resolve(ndarray);
} else if (req.status == 404) {
console.log("yup");
reject({ is404: true });
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.open("GET", url, true);
req.responseType = "arraybuffer";
req.send(null);
});
}
return {
open: open,
promise: promise,
fromArrayBuffer: fromArrayBuffer
};
})();
module.exports = NumpyLoader;

View File

@ -1,18 +1,18 @@
export function swapKeysAndValues(obj, f) {
return Object.keys(obj).reduce(function(acc, k) {
acc[obj[k]] = k;
return acc
},{})
};
return acc;
}, {});
}
export function fromArray(arr) {
// arr is an array of array key-value pairs
// like [['a', 1], ['b', 2]]
// arr is an array of array key-value pairs
// like [['a', 1], ['b', 2]]
const pairs = arr.map( ([k,v]) => ({[k]: v}));
if(pairs.length > 0) {
return Object.assign(...pairs);
} else {
return {};
}
const pairs = arr.map(([k, v]) => ({ [k]: v }));
if (pairs.length > 0) {
return Object.assign(...pairs);
} else {
return {};
}
}

View File

@ -1,9 +1,8 @@
export function hash(str) {
var hash = 5381,
i = str.length;
i = str.length;
while(i) {
while (i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}

View File

@ -1,173 +1,184 @@
import {createClassFromSpec} from 'react-vega';
import { createClassFromSpec } from "react-vega";
const canHistogramSpec = {
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 1000,
"height": 100,
"padding": 5,
$schema: "https://vega.github.io/schema/vega/v3.0.json",
width: 1000,
height: 100,
padding: 5,
"signals": [
signals: [
{
"name": "segment"
name: "segment"
}
],
"data": [
data: [
{
"name": "binned"
name: "binned"
}
],
"scales": [
scales: [
{
"name": "xscale",
"type": "linear",
"zero": false,
"range": "width",
"domain": {"data": "binned", "field": "startTime"}
name: "xscale",
type: "linear",
zero: false,
range: "width",
domain: { data: "binned", field: "startTime" }
},
{
"name": "xrelscale",
"type": "linear",
"zero": false,
"range": "width",
"domain": {"data": "binned", "field": "relStartTime"}
name: "xrelscale",
type: "linear",
zero: false,
range: "width",
domain: { data: "binned", field: "relStartTime" }
},
{
"name": "yscale",
"type": "linear",
"range": "height", "round": true,
"domain": {"data": "binned", "field": "count"},
"zero": true, "nice": true
name: "yscale",
type: "linear",
range: "height",
round: true,
domain: { data: "binned", field: "count" },
zero: true,
nice: true
}
],
"axes": [
{"orient": "bottom", "scale": "xrelscale", "zindex": 1, "tickCount": 10},
{"orient": "left", "scale": "yscale", "tickCount": 5, "zindex": 1}
axes: [
{ orient: "bottom", scale: "xrelscale", zindex: 1, tickCount: 10 },
{ orient: "left", scale: "yscale", tickCount: 5, zindex: 1 }
],
"marks": [
{"type": "group",
"name": "histogram",
"interactive": true,
"encode": {
"enter": {
"height": {"value": 75},
"width": {"value": 1000},
"fill": {"value": "transparent"}
}
},
"signals": [
marks: [
{
type: "group",
name: "histogram",
interactive: true,
encode: {
enter: {
height: { value: 75 },
width: { value: 1000 },
fill: { value: "transparent" }
}
},
signals: [
{
"name": "brush", "value": 0,
"on": [
name: "brush",
value: 0,
on: [
{
"events": "@bins:mousedown",
"update": "[x(), x()]"
events: "@bins:mousedown",
update: "[x(), x()]"
},
{
"events": "[@bins:mousedown, window:mouseup] > window:mousemove!",
"update": "[brush[0], clamp(x(), 0, width)]"
events: "[@bins:mousedown, window:mouseup] > window:mousemove!",
update: "[brush[0], clamp(x(), 0, width)]"
},
{
"events": {"signal": "delta"},
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
events: { signal: "delta" },
update:
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
}
]
},
{
"name": "anchor", "value": null,
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
name: "anchor",
value: null,
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
},
{
"name": "xdown", "value": 0,
"on": [{"events": "@brush:mousedown", "update": "x()"}]
name: "xdown",
value: 0,
on: [{ events: "@brush:mousedown", update: "x()" }]
},
{
"name": "delta", "value": 0,
"on": [
name: "delta",
value: 0,
on: [
{
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
"update": "x() - xdown"
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
update: "x() - xdown"
}
]
},
{
"name": "segment",
"push": "outer",
"on": [
name: "segment",
push: "outer",
on: [
{
"events": {"signal": "brush"},
"update": "span(brush) ? invert('xscale', brush) : null"
events: { signal: "brush" },
update: "span(brush) ? invert('xscale', brush) : null"
}
]
}
],
"marks": [
{
"type": "rect",
"interactive": true,
"from": {"data": "binned"},
"name": "bins",
"encode": {
"update": {
"x": {"scale": "xscale", "field": "startTime"},
"x2": {"scale": "xscale", "field": "endTime",
"offset": 0},
"y": {"scale": "yscale", "field": "count"},
"y2": {"scale": "yscale", "value": 0},
"fill": {"value": "steelblue"}
],
marks: [
{
type: "rect",
interactive: true,
from: { data: "binned" },
name: "bins",
encode: {
update: {
x: { scale: "xscale", field: "startTime" },
x2: {
scale: "xscale",
field: "endTime",
offset: 0
},
y: { scale: "yscale", field: "count" },
y2: { scale: "yscale", value: 0 },
fill: { value: "steelblue" }
},
"hover": {
"fill": {"value": "goldenrod"},
"cursor": {"value": "ew-resize"}
hover: {
fill: { value: "goldenrod" },
cursor: { value: "ew-resize" }
}
}
},
{
"type": "rect",
"interactive": true,
"name": "brush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"fill": {"value": "#333"},
"fillOpacity": {"value": 0.2}
type: "rect",
interactive: true,
name: "brush",
encode: {
enter: {
y: { value: 0 },
height: { value: 100 },
fill: { value: "#333" },
fillOpacity: { value: 0.2 }
},
"update": {
"x": {"signal": "brush[0]"},
"x2": {"signal": "brush[1]"}
update: {
x: { signal: "brush[0]" },
x2: { signal: "brush[1]" }
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"width": {"value": 2},
"fill": {"value": "firebrick"}
type: "rect",
interactive: false,
encode: {
enter: {
y: { value: 0 },
height: { value: 100 },
width: { value: 2 },
fill: { value: "firebrick" }
},
"update": {
"x": {"signal": "brush[0]"}
update: {
x: { signal: "brush[0]" }
}
}
},
{
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"y": {"value": 0},
"height": {"value": 100},
"width": {"value": 2},
"fill": {"value": "firebrick"}
type: "rect",
interactive: false,
encode: {
enter: {
y: { value: 0 },
height: { value: 100 },
width: { value: 2 },
fill: { value: "firebrick" }
},
"update": {
"x": {"signal": "brush[1]"}
update: {
x: { signal: "brush[1]" }
}
}
}

View File

@ -1,306 +1,334 @@
import {createClassFromSpec} from 'react-vega';
import { createClassFromSpec } from "react-vega";
export default createClassFromSpec('CanPlot', {
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 500,
"height": 200,
"padding": "auto",
"autosize": {"type": "fit", "resize": true},
"signals": [
export default createClassFromSpec("CanPlot", {
$schema: "https://vega.github.io/schema/vega/v3.0.json",
width: 500,
height: 200,
padding: "auto",
autosize: { type: "fit", resize: true },
signals: [
{
"name": "tipTime",
"on": [{
"events": "mousemove",
"update": "invert('xrelscale', x())"
}]
},
{"name": "clickTime",
"on": [{
"events": "mouseup",
"update": "invert('xrelscale', x())"
}]
},
{"name": "videoTime"},
{"name": "segment",
"value": {"data": "table", "field": "relTime"}
}
],
"data": [
{
"name": "table"
},
{
"name": "tooltip",
"source": "table",
"transform": [
name: "tipTime",
on: [
{
"type": "filter",
"expr": "abs(datum.relTime - tipTime) <= 0.01"
},
{
"type": "aggregate",
"fields": ["relTime", "y", "unit"],
"ops": ["min", "argmin", "argmin"],
"as": ["min", "argmin", "argmin"]
events: "mousemove",
update: "invert('xrelscale', x())"
}
]
},
{
"name": "ySegmentScale",
"source": "table",
"transform": [
name: "clickTime",
on: [
{
"type": "filter",
"expr": "length(segment) != 2 || (datum.relTime >= segment[0] && datum.relTime <= segment[1])"
events: "mouseup",
update: "invert('xrelscale', x())"
}
]
},
{ name: "videoTime" },
{
name: "segment",
value: { data: "table", field: "relTime" }
}
],
data: [
{
name: "table"
},
{
name: "tooltip",
source: "table",
transform: [
{
type: "filter",
expr: "abs(datum.relTime - tipTime) <= 0.01"
},
{"type": "extent", "field": "y", "signal": "ySegment"}
{
type: "aggregate",
fields: ["relTime", "y", "unit"],
ops: ["min", "argmin", "argmin"],
as: ["min", "argmin", "argmin"]
}
]
},
{
name: "ySegmentScale",
source: "table",
transform: [
{
type: "filter",
expr:
"length(segment) != 2 || (datum.relTime >= segment[0] && datum.relTime <= segment[1])"
},
{ type: "extent", field: "y", signal: "ySegment" }
]
}
],
"scales": [
scales: [
{
"name": "xscale",
"type": "linear",
"range": "width",
"domain": {"data": "table", "field": "x"},
"zero": false
name: "xscale",
type: "linear",
range: "width",
domain: { data: "table", field: "x" },
zero: false
},
{
"name": "xrelscale",
"type": "linear",
"range": "width",
"domain": {"data": "table", "field": "relTime"},
"zero": false,
"clamp": true,
"domainRaw": {"signal": "segment"}
name: "xrelscale",
type: "linear",
range: "width",
domain: { data: "table", field: "relTime" },
zero: false,
clamp: true,
domainRaw: { signal: "segment" }
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"clamp": true,
"zero": false,
"domain": {"signal": "ySegment"}
name: "yscale",
type: "linear",
range: "height",
clamp: true,
zero: false,
domain: { signal: "ySegment" }
},
{
"name": "color",
"type": "ordinal",
"domain": {"data": "table", "field": "color"},
"range": {"data": "table", "field": "color"}
name: "color",
type: "ordinal",
domain: { data: "table", field: "color" },
range: { data: "table", field: "color" }
}
],
"axes": [
{"orient": "bottom", "scale": "xrelscale", labelOverlap: true},
{"orient": "left", "scale": "yscale"}
axes: [
{ orient: "bottom", scale: "xrelscale", labelOverlap: true },
{ orient: "left", scale: "yscale" }
],
"marks": [
{"type": "group",
"name": "plot",
"interactive": true,
"encode": {
"enter": {
"width": {"signal": "width"},
"height": {"signal": "height"},
"fill": {"value": "transparent"}
}
},
"signals": [
marks: [
{
type: "group",
name: "plot",
interactive: true,
encode: {
enter: {
width: { signal: "width" },
height: { signal: "height" },
fill: { value: "transparent" }
}
},
signals: [
{
"name": "brush", "value": 0,
"on": [
name: "brush",
value: 0,
on: [
{
"events": "@boundingRect:mousedown",
"update": "[x(), x()]"
events: "@boundingRect:mousedown",
update: "[x(), x()]"
},
{
"events": "[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
"update": "[brush[0], clamp(x(), 0, width)]"
events:
"[@boundingRect:mousedown, window:mouseup] > window:mousemove!",
update: "[brush[0], clamp(x(), 0, width)]"
},
{
"events": {"signal": "delta"},
"update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
events: { signal: "delta" },
update:
"clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
}
]
},
{
"name": "anchor", "value": null,
"on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
name: "anchor",
value: null,
on: [{ events: "@brush:mousedown", update: "slice(brush)" }]
},
{
"name": "xdown", "value": 0,
"on": [{"events": "@brush:mousedown", "update": "x()"}]
name: "xdown",
value: 0,
on: [{ events: "@brush:mousedown", update: "x()" }]
},
{
"name": "delta", "value": 0,
"on": [
name: "delta",
value: 0,
on: [
{
"events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
"update": "x() - xdown"
events: "[@brush:mousedown, window:mouseup] > window:mousemove!",
update: "x() - xdown"
}
]
},
{
"name": "segment",
"push": "outer",
"on": [
name: "segment",
push: "outer",
on: [
{
"events": "window:mouseup",
"update": "span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
events: "window:mouseup",
update:
"span(brush) && span(brush) > 15 ? invert('xrelscale', brush) : segment"
}
]
}
],
"marks": [
{
"type": "group",
"from": {
"facet": {
"name": "series",
"data": "table",
"groupby": "color"
],
marks: [
{
type: "group",
from: {
facet: {
name: "series",
data: "table",
groupby: "color"
}
},
marks: {
type: "line",
name: "lineMark",
from: { data: "series" },
interactive: true,
encode: {
update: {
interpolate: { value: "step" },
x: { scale: "xrelscale", field: "relTime" },
y: { scale: "yscale", field: "y" }
},
hover: {
fillOpacity: { value: 0.5 }
},
enter: {
clip: { value: true },
stroke: { scale: "color", field: "color" },
strokeWidth: { value: 2 }
}
}
}
},
{
type: "rect",
interactive: true,
name: "brush",
encode: {
enter: {
y: { value: 0 },
height: { signal: "height" },
fill: { value: "#333" },
fillOpacity: { value: 0.2 }
},
"marks": {
"type": "line",
"name": "lineMark",
"from": {"data": "series"},
"interactive": true,
"encode": {
"update": {
"interpolate": {"value": "step"},
"x": {"scale": "xrelscale", "field": "relTime"},
"y": {"scale": "yscale", "field": "y"}
update: {
x: { signal: "brush[0]" },
x2: { signal: "brush[1]" }
}
}
},
// {
// "type": "rect",
// "interactive": false,
// "encode": {
// "enter": {
// "y": {"value": 0},
// "height": {"value": 200},
// "width": {"value": 2},
// "fill": {"value": "firebrick"}
// },
// "update": {
// "x": {"signal": "brush[0]"}
// }
// }
// },
// {
// "type": "rect",
// "interactive": false,
// "encode": {
// "enter": {
// "y": {"value": 0},
// "height": {"value": 200},
// "width": {"value": 2},
// "fill": {"value": "firebrick"}
// },
// "update": {
// "x": {"signal": "brush[1]"}
// }
// }
// },
{
type: "rule",
encode: {
update: {
y: { value: 0 },
y2: { field: { group: "height" } },
stroke: { value: "#000" },
strokeWidth: { value: 2 },
x: {
scale: "xrelscale",
signal: "videoTime",
offset: 0.5
}
}
}
},
{
type: "symbol",
from: { data: "tooltip" },
encode: {
update: {
x: { scale: "xrelscale", field: "argmin.relTime" },
y: { scale: "yscale", field: "argmin.y" },
size: { value: 50 },
fill: { value: "black" }
}
}
},
{
type: "group",
from: { data: "tooltip" },
interactive: false,
name: "tooltipGroup",
encode: {
update: {
x: [
{
test:
"inrange(datum.argmin.relTime + 80, domain('xrelscale'))",
scale: "xrelscale",
field: "argmin.relTime"
},
"hover": {
"fillOpacity": {"value": 0.5}
},
"enter": {
"clip": {"value": true},
"stroke": {"scale": "color", "field": "color"},
"strokeWidth": {"value": 2}
{ scale: "xrelscale", field: "argmin.relTime", offset: -80 }
],
y: { scale: "yscale", field: "argmin.y" },
height: { value: 20 },
width: { value: 80 },
fill: { value: "#fff" },
fillOpacity: { value: 0.85 },
stroke: { value: "#aaa" },
strokeWidth: { value: 0.5 }
}
},
marks: [
{
type: "text",
interactive: false,
encode: {
update: {
text: {
signal:
"format(parent.argmin.relTime, ',.2f') + ': ' + format(parent.argmin.y, ',.2f') + ' ' + parent.argmin.unit"
},
fill: { value: "black" },
fontWeight: { value: "bold" },
y: { value: 20 }
}
}
},
},
{
"type": "rect",
"interactive": true,
"name": "brush",
"encode": {
"enter": {
"y": {"value": 0},
"height": {"signal": "height"},
"fill": {"value": "#333"},
"fillOpacity": {"value": 0.2}
},
"update": {
"x": {"signal": "brush[0]"},
"x2": {"signal": "brush[1]"}
}
}
},
// {
// "type": "rect",
// "interactive": false,
// "encode": {
// "enter": {
// "y": {"value": 0},
// "height": {"value": 200},
// "width": {"value": 2},
// "fill": {"value": "firebrick"}
// },
// "update": {
// "x": {"signal": "brush[0]"}
// }
// }
// },
// {
// "type": "rect",
// "interactive": false,
// "encode": {
// "enter": {
// "y": {"value": 0},
// "height": {"value": 200},
// "width": {"value": 2},
// "fill": {"value": "firebrick"}
// },
// "update": {
// "x": {"signal": "brush[1]"}
// }
// }
// },
{
"type": "rule",
"encode": {
"update": {
"y": {"value": 0},
"y2": {"field": {"group": "height"}},
"stroke": {"value": "#000"},
"strokeWidth": {"value": 2},
"x": {"scale": "xrelscale",
"signal": "videoTime", "offset": 0.5}
}
}
},
{
"type": "symbol",
"from": {"data": "tooltip"},
"encode": {
"update": {
"x": {"scale": "xrelscale", "field": "argmin.relTime"},
"y": {"scale": "yscale", "field": "argmin.y"},
"size": {"value": 50},
"fill": {"value": "black"}
}
}
},
{
"type": "group",
"from": {"data": "tooltip"},
"interactive": false,
"name": "tooltipGroup",
"encode": {
"update": {
"x": [{"test": "inrange(datum.argmin.relTime + 80, domain('xrelscale'))", "scale": "xrelscale", "field": "argmin.relTime"},
{"scale": "xrelscale", "field": "argmin.relTime", "offset": -80}],
"y": {"scale": "yscale", "field": "argmin.y"},
"height": {"value": 20},
"width": {"value": 80},
"fill": {"value": "#fff"},
"fillOpacity": {"value": 0.85},
"stroke": {"value": "#aaa"},
"strokeWidth": {"value": 0.5}
}
}
]
},
"marks": [
{
"type": "text",
"interactive": false,
"encode": {
"update": {
"text": {"signal": "format(parent.argmin.relTime, ',.2f') + ': ' + format(parent.argmin.y, ',.2f') + ' ' + parent.argmin.unit"},
"fill": {"value": "black"},
"fontWeight": {"value": "bold"},
"y": {"value": 20}
}
{
type: "rect",
name: "boundingRect",
interactive: true,
encode: {
enter: {
width: { signal: "width" },
height: { signal: "height" },
fill: { value: "transparent" }
}
}
]
},
{
"type": "rect",
"name": "boundingRect",
"interactive": true,
"encode": {
"enter": {
"width": {"signal": "width"},
"height": {"signal": "height"},
"fill": {"value": "transparent"}
}
}
},
]
]
}
]
});

687
yarn.lock

File diff suppressed because it is too large Load Diff