Refactor Signals
Signals are tied to OpenDBC and CanJS work. Refactor them to make space for hosting the functions currently in CanJS that shouldn't be in CanJS. Also the beginning of performance work for Cabana, which is sorely needed due to constant large state updates triggered by raf on video frame eventsmain
parent
48a04b3d49
commit
ddeea37287
|
@ -68,16 +68,6 @@ export default class AddSignals extends Component {
|
||||||
dragSignal: null,
|
dragSignal: null,
|
||||||
dragCurrentBit: null
|
dragCurrentBit: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateSignalStyles = this.updateSignalStyles.bind(this);
|
|
||||||
this.onSignalHover = this.onSignalHover.bind(this);
|
|
||||||
this.onBitHover = this.onBitHover.bind(this);
|
|
||||||
this.onSignalHoverEnd = this.onSignalHoverEnd.bind(this);
|
|
||||||
this.onTentativeSignalChange = this.onTentativeSignalChange.bind(this);
|
|
||||||
this.onSignalChange = this.onSignalChange.bind(this);
|
|
||||||
this.onSignalRemove = this.onSignalRemove.bind(this);
|
|
||||||
this.onSignalPlotChange = this.onSignalPlotChange.bind(this);
|
|
||||||
this.resetDragState = this.resetDragState.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copySignals(signals) {
|
copySignals(signals) {
|
||||||
|
@ -124,11 +114,11 @@ export default class AddSignals extends Component {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSignalStyles() {
|
updateSignalStyles = () => {
|
||||||
const signalStyles = this.calcSignalStyles(this.state.signals);
|
const signalStyles = this.calcSignalStyles(this.state.signals);
|
||||||
|
|
||||||
this.setState({ signalStyles });
|
this.setState({ signalStyles });
|
||||||
}
|
};
|
||||||
|
|
||||||
calcSignalStyles(signals) {
|
calcSignalStyles(signals) {
|
||||||
const signalStyles = {};
|
const signalStyles = {};
|
||||||
|
@ -160,11 +150,11 @@ export default class AddSignals extends Component {
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
onSignalHover(signal) {
|
onSignalHover = signal => {
|
||||||
if (!signal) return;
|
if (!signal) return;
|
||||||
|
|
||||||
this.setState({ highlightedSignal: signal.name }, this.updateSignalStyles);
|
this.setState({ highlightedSignal: signal.name }, this.updateSignalStyles);
|
||||||
}
|
};
|
||||||
|
|
||||||
signalBitIndex(bitIdx, signal) {
|
signalBitIndex(bitIdx, signal) {
|
||||||
// todo does this work for both big and little endian?
|
// todo does this work for both big and little endian?
|
||||||
|
@ -175,7 +165,7 @@ export default class AddSignals extends Component {
|
||||||
return bitIdx - startBit;
|
return bitIdx - startBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBitHover(bitIdx, signal) {
|
onBitHover = (bitIdx, signal) => {
|
||||||
let { dragStartBit, signals, dragSignal } = this.state;
|
let { dragStartBit, signals, dragSignal } = this.state;
|
||||||
|
|
||||||
if (dragStartBit !== null) {
|
if (dragStartBit !== null) {
|
||||||
|
@ -279,13 +269,13 @@ export default class AddSignals extends Component {
|
||||||
if (signal) {
|
if (signal) {
|
||||||
this.onSignalHover(signal);
|
this.onSignalHover(signal);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onSignalHoverEnd(signal) {
|
onSignalHoverEnd = signal => {
|
||||||
if (!signal) return;
|
if (!signal) return;
|
||||||
|
|
||||||
this.setState({ highlightedSignal: null }, this.updateSignalStyles);
|
this.setState({ highlightedSignal: null }, this.updateSignalStyles);
|
||||||
}
|
};
|
||||||
|
|
||||||
nextNewSignalName() {
|
nextNewSignalName() {
|
||||||
const existingNames = Object.keys(this.state.signals);
|
const existingNames = Object.keys(this.state.signals);
|
||||||
|
@ -440,10 +430,11 @@ export default class AddSignals extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBitMatrix() {
|
renderBitMatrix() {
|
||||||
|
const { message } = this.props;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
let rowCount;
|
let rowCount;
|
||||||
if (this.props.message.frame && this.props.message.frame.size) {
|
if (message.frame && message.frame.size) {
|
||||||
rowCount = Math.floor(this.props.message.frame.size * 8 / 8);
|
rowCount = Math.floor(message.frame.size * 8 / 8);
|
||||||
} else {
|
} else {
|
||||||
rowCount = 8;
|
rowCount = 8;
|
||||||
}
|
}
|
||||||
|
@ -483,11 +474,7 @@ export default class AddSignals extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rowBits.push(
|
rowBits.push(<td key={"hex-repr"}>{this.byteValueHex(i)}</td>);
|
||||||
<td key={"hex-repr"} className={css(Styles.hex)}>
|
|
||||||
{this.byteValueHex(i)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
rows.push(<tr key={i.toString()}>{rowBits}</tr>);
|
rows.push(<tr key={i.toString()}>{rowBits}</tr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,23 +487,23 @@ export default class AddSignals extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDragState() {
|
resetDragState = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
dragStartBit: null,
|
dragStartBit: null,
|
||||||
dragSignal: null,
|
dragSignal: null,
|
||||||
dragCurrentBit: null
|
dragCurrentBit: null
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onTentativeSignalChange(signal) {
|
onTentativeSignalChange = signal => {
|
||||||
// Tentative signal changes are not propagated up
|
// Tentative signal changes are not propagated up
|
||||||
// but their effects are displayed in the bitmatrix
|
// but their effects are displayed in the bitmatrix
|
||||||
const { signals } = this.state;
|
const { signals } = this.state;
|
||||||
signals[signal.name] = signal;
|
signals[signal.name] = signal;
|
||||||
this.setState({ signals });
|
this.setState({ signals });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSignalChange(signal, oldSignal) {
|
onSignalChange = (signal, oldSignal) => {
|
||||||
const { signals } = this.state;
|
const { signals } = this.state;
|
||||||
|
|
||||||
for (let signalName in signals) {
|
for (let signalName in signals) {
|
||||||
|
@ -527,13 +514,13 @@ export default class AddSignals extends Component {
|
||||||
signals[signal.name] = signal;
|
signals[signal.name] = signal;
|
||||||
|
|
||||||
this.setState({ signals }, this.propagateUpSignalChange);
|
this.setState({ signals }, this.propagateUpSignalChange);
|
||||||
}
|
};
|
||||||
|
|
||||||
onSignalRemove(signal) {
|
onSignalRemove = signal => {
|
||||||
const { signals } = this.state;
|
const { signals } = this.state;
|
||||||
delete signals[signal.name];
|
delete signals[signal.name];
|
||||||
this.setState({ signals }, this.propagateUpSignalChange);
|
this.setState({ signals }, this.propagateUpSignalChange);
|
||||||
}
|
};
|
||||||
|
|
||||||
propagateUpSignalChange() {
|
propagateUpSignalChange() {
|
||||||
const { signals } = this.state;
|
const { signals } = this.state;
|
||||||
|
@ -544,11 +531,11 @@ export default class AddSignals extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSignalPlotChange(shouldPlot, signalUid) {
|
onSignalPlotChange = (shouldPlot, signalUid) => {
|
||||||
const { message } = this.props;
|
const { message } = this.props;
|
||||||
|
|
||||||
this.props.onSignalPlotChange(shouldPlot, message.id, signalUid);
|
this.props.onSignalPlotChange(shouldPlot, message.id, signalUid);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,15 +18,11 @@ export default class SignalLegend extends Component {
|
||||||
plottedSignalUids: PropTypes.array
|
plottedSignalUids: PropTypes.array
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
expandedSignals: []
|
||||||
this.state = {
|
};
|
||||||
expandedSignals: []
|
|
||||||
};
|
|
||||||
this.toggleExpandSignal = this.toggleExpandSignal.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleExpandSignal(s) {
|
toggleExpandSignal = s => {
|
||||||
const { expandedSignals } = this.state;
|
const { expandedSignals } = this.state;
|
||||||
if (!expandedSignals.includes(s.uid)) {
|
if (!expandedSignals.includes(s.uid)) {
|
||||||
const updatedExpandedSignals = [...expandedSignals, s.uid];
|
const updatedExpandedSignals = [...expandedSignals, s.uid];
|
||||||
|
@ -35,7 +31,7 @@ export default class SignalLegend extends Component {
|
||||||
const updatedExpandedSignals = expandedSignals.filter(i => i !== s.uid);
|
const updatedExpandedSignals = expandedSignals.filter(i => i !== s.uid);
|
||||||
this.setState({ expandedSignals: updatedExpandedSignals });
|
this.setState({ expandedSignals: updatedExpandedSignals });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
checkExpandedSignal(s) {
|
checkExpandedSignal(s) {
|
||||||
return this.state.expandedSignals.includes(s);
|
return this.state.expandedSignals.includes(s);
|
||||||
|
|
|
@ -42,19 +42,6 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.is-highlighted {
|
|
||||||
.signals-legend-entry-color {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-color {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0.3;
|
|
||||||
position: absolute;
|
|
||||||
width: 1.5%;
|
|
||||||
}
|
|
||||||
&-header {
|
&-header {
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -1,398 +0,0 @@
|
||||||
// SignalLegendEntry.js
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
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 unsignedTransformation = field => {
|
|
||||||
return (value, signal) => {
|
|
||||||
if (value !== "") {
|
|
||||||
value = Number(value) || 0;
|
|
||||||
|
|
||||||
if (value < 0) {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signal[field] = value;
|
|
||||||
return signal;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateField(fieldSpec, value) {
|
|
||||||
let { signalEdited } = this.state;
|
|
||||||
const { signal } = this.props;
|
|
||||||
|
|
||||||
if (fieldSpec.transform) {
|
|
||||||
signalEdited = fieldSpec.transform(value, signalEdited);
|
|
||||||
} else {
|
|
||||||
signalEdited[fieldSpec.field] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderNumberField(fieldSpec, signal) {
|
|
||||||
const { field, title } = fieldSpec;
|
|
||||||
let valueCol;
|
|
||||||
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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="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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import styled from "react-emotion";
|
||||||
|
|
||||||
|
// color bar on the left side of the signals legend
|
||||||
|
export default styled("div")`
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1.5%;
|
||||||
|
opacity: ${({ isHighlighted }) => (isHighlighted ? 0.5 : 0.3)};
|
||||||
|
background-color: rgb(${({ rgb }) => rgb.join(",")});
|
||||||
|
`;
|
|
@ -0,0 +1,113 @@
|
||||||
|
import DbcUtils from "../../utils/dbc";
|
||||||
|
|
||||||
|
const unsignedTransformation = field => {
|
||||||
|
return (value, signal) => {
|
||||||
|
if (value !== "") {
|
||||||
|
value = Number(value) || 0;
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal[field] = value;
|
||||||
|
return signal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
field: "name",
|
||||||
|
title: "Name",
|
||||||
|
type: "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "size",
|
||||||
|
title: "Size",
|
||||||
|
type: "number",
|
||||||
|
transform: unsignedTransformation("size")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "startBit",
|
||||||
|
title: signal =>
|
||||||
|
signal.isLittleEndian ? "Least significant bit" : "Most significant bit",
|
||||||
|
type: "number",
|
||||||
|
transform: 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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
];
|
|
@ -0,0 +1,13 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
|
||||||
|
export default class Field extends Component {
|
||||||
|
render() {
|
||||||
|
const { title, htmlFor, children } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="form-field form-field--small">
|
||||||
|
<label htmlFor={htmlFor}>{title}</label>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Field from "./Field";
|
||||||
|
import FIELDS from "./FIELDS";
|
||||||
|
import { swapKeysAndValues } from "../../utils/object";
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
signal,
|
||||||
|
onSignalRemove,
|
||||||
|
isExpanded,
|
||||||
|
getSignalEdited,
|
||||||
|
update
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="signals-legend-entry-form">
|
||||||
|
{FIELDS.map(field => {
|
||||||
|
// console.log(field, getSignalEdited(field.field))
|
||||||
|
return (
|
||||||
|
<div className="signals-legend-entry-form-field" key={field.field}>
|
||||||
|
<FieldNode
|
||||||
|
field={field}
|
||||||
|
signal={signal}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
signalEdited={getSignalEdited(field)}
|
||||||
|
update={update}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="signals-legend-entry-form-remove">
|
||||||
|
<button
|
||||||
|
className="button--tiny button--alpha"
|
||||||
|
onClick={() => onSignalRemove(signal)}
|
||||||
|
>
|
||||||
|
Remove Signal
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NumberField = ({
|
||||||
|
fieldSpec,
|
||||||
|
signal,
|
||||||
|
isExpanded,
|
||||||
|
signalEdited,
|
||||||
|
updateField
|
||||||
|
}) => {
|
||||||
|
const { field, title } = fieldSpec;
|
||||||
|
const htmlFor = `${signal.name}_${field}`;
|
||||||
|
let valueCol;
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
let value = signalEdited;
|
||||||
|
if (value !== "") {
|
||||||
|
let num = Number(value);
|
||||||
|
value = isNaN(num) ? "" : num;
|
||||||
|
}
|
||||||
|
valueCol = (
|
||||||
|
<input
|
||||||
|
id={htmlFor}
|
||||||
|
type="number"
|
||||||
|
value={value}
|
||||||
|
onChange={e => {
|
||||||
|
updateField(fieldSpec, e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let value = signal[field];
|
||||||
|
valueCol = <span>{value}</span>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Field
|
||||||
|
title={typeof title === "function" ? title(signal) : title}
|
||||||
|
htmlFor={htmlFor}
|
||||||
|
>
|
||||||
|
{valueCol}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StringField = ({
|
||||||
|
fieldSpec,
|
||||||
|
signal,
|
||||||
|
isExpanded,
|
||||||
|
signalEdited,
|
||||||
|
updateField
|
||||||
|
}) => {
|
||||||
|
const { field, title } = fieldSpec;
|
||||||
|
const htmlFor = `${signal.name}_${field}`;
|
||||||
|
let valueCol;
|
||||||
|
if (isExpanded) {
|
||||||
|
valueCol = (
|
||||||
|
<input
|
||||||
|
id={htmlFor}
|
||||||
|
type="text"
|
||||||
|
value={signalEdited || ""}
|
||||||
|
onChange={e => {
|
||||||
|
updateField(fieldSpec, e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
valueCol = <span>{signal[field]}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field
|
||||||
|
title={typeof title === "function" ? title(signal) : title}
|
||||||
|
htmlFor={htmlFor}
|
||||||
|
>
|
||||||
|
{valueCol}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const OptionField = ({
|
||||||
|
fieldSpec,
|
||||||
|
signal,
|
||||||
|
isExpanded,
|
||||||
|
signalEdited,
|
||||||
|
updateField
|
||||||
|
}) => {
|
||||||
|
let valueCol;
|
||||||
|
const { field, title } = fieldSpec;
|
||||||
|
const htmlFor = `${signal.name}_${field}`;
|
||||||
|
const { options, optionValues } = fieldSpec.options;
|
||||||
|
let valueOptions = swapKeysAndValues(optionValues);
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
const optionEles = options.map(opt => (
|
||||||
|
<option key={opt} value={optionValues[opt]}>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
));
|
||||||
|
valueCol = (
|
||||||
|
<select
|
||||||
|
id={htmlFor}
|
||||||
|
defaultValue={signalEdited}
|
||||||
|
onChange={e => {
|
||||||
|
updateField(fieldSpec, e.target.value === "true");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{optionEles}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
valueCol = <span>{valueOptions[signal[field]]}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field
|
||||||
|
title={typeof title === "function" ? title(signal) : title}
|
||||||
|
htmlFor={htmlFor}
|
||||||
|
>
|
||||||
|
{valueCol}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FieldNode = ({ field, signal, isExpanded, signalEdited, update }) => {
|
||||||
|
switch (field.type) {
|
||||||
|
case "number":
|
||||||
|
return (
|
||||||
|
<NumberField
|
||||||
|
fieldSpec={field}
|
||||||
|
signal={signal}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
signalEdited={signalEdited}
|
||||||
|
updateField={update}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "option":
|
||||||
|
return (
|
||||||
|
<OptionField
|
||||||
|
fieldSpec={field}
|
||||||
|
signal={signal}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
signalEdited={signalEdited}
|
||||||
|
updateField={update}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "string":
|
||||||
|
return (
|
||||||
|
<StringField
|
||||||
|
fieldSpec={field}
|
||||||
|
signal={signal}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
signalEdited={signalEdited}
|
||||||
|
updateField={update}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,151 @@
|
||||||
|
// SignalLegendEntry.js
|
||||||
|
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import cx from "classnames";
|
||||||
|
|
||||||
|
import Signal from "../../models/can/signal";
|
||||||
|
import SignalForm from "./SignalForm";
|
||||||
|
import ColorBar from "./ColorBar";
|
||||||
|
|
||||||
|
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 fieldSpecForName = name => {
|
||||||
|
return SignalLegendEntry.fields.find(field => field.field === name);
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
signalEdited: Object.assign(Object.create(props.signal), props.signal)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (!nextProps.signal.equals(this.props.signal)) {
|
||||||
|
this.setState({
|
||||||
|
signalEdited: Object.assign(
|
||||||
|
Object.create(nextProps.signal),
|
||||||
|
nextProps.signal
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateField = (fieldSpec, value) => {
|
||||||
|
let { signalEdited } = this.state;
|
||||||
|
const { signal } = this.props;
|
||||||
|
|
||||||
|
if (fieldSpec.transform) {
|
||||||
|
signalEdited = fieldSpec.transform(value, signalEdited);
|
||||||
|
} else {
|
||||||
|
signalEdited[fieldSpec.field] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleSignalPlot = e => {
|
||||||
|
const { signal, isPlotted } = this.props;
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onSignalPlotChange(!isPlotted, signal.uid);
|
||||||
|
};
|
||||||
|
|
||||||
|
getSignalEdited = field => {
|
||||||
|
return this.state.signalEdited[field];
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
const { signal, isHighlighted, color, isPlotted, isExpanded } = this.props;
|
||||||
|
const expandedEntryClass = isExpanded ? "is-expanded" : null;
|
||||||
|
const plottedButtonClass = isPlotted ? "button" : "button--alpha";
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx("signals-legend-entry", expandedEntryClass)}
|
||||||
|
onMouseEnter={() => this.props.onSignalHover(signal)}
|
||||||
|
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}
|
||||||
|
>
|
||||||
|
<ColorBar isHighlighted={isHighlighted} rgb={color} />
|
||||||
|
<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)}>
|
||||||
|
{isPlotted ? "Hide Plot" : "Show Plot"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="signals-legend-entry-body">
|
||||||
|
{isExpanded && (
|
||||||
|
<SignalForm
|
||||||
|
signal={signal}
|
||||||
|
onSignalRemove={this.props.onSignalRemove}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
getSignalEdited={this.getSignalEdited}
|
||||||
|
update={this.updateField}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue