commit
7c38acbcbe
|
@ -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,43 @@
|
||||||
|
import React from "react";
|
||||||
|
import Field from "./Field";
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import Field from "./Field";
|
||||||
|
|
||||||
|
import { swapKeysAndValues } from "../../utils/object";
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import FIELDS from "./FIELDS";
|
||||||
|
import NumberField from "./NumberField";
|
||||||
|
import StringField from "./StringField";
|
||||||
|
import OptionField from "./OptionField";
|
||||||
|
|
||||||
|
const FieldMap = {
|
||||||
|
number: NumberField,
|
||||||
|
option: OptionField,
|
||||||
|
string: StringField
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
signal,
|
||||||
|
onSignalRemove,
|
||||||
|
isExpanded,
|
||||||
|
getSignalEdited,
|
||||||
|
update
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="signals-legend-entry-form">
|
||||||
|
{FIELDS.map(field => {
|
||||||
|
const Node = FieldMap[field.type];
|
||||||
|
return (
|
||||||
|
<div className="signals-legend-entry-form-field" key={field.field}>
|
||||||
|
<Node
|
||||||
|
fieldSpec={field}
|
||||||
|
signal={signal}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
signalEdited={getSignalEdited(field.field)}
|
||||||
|
updateField={update}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="signals-legend-entry-form-remove">
|
||||||
|
<button
|
||||||
|
className="button--tiny button--alpha"
|
||||||
|
onClick={() => onSignalRemove(signal)}
|
||||||
|
>
|
||||||
|
Remove Signal
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
import Field from "./Field";
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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";
|
||||||
|
import FIELDS from "./FIELDS";
|
||||||
|
|
||||||
|
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 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import SignalLegendEntry from "../../components/SignalLegendEntry";
|
import SignalLegendEntry from ".";
|
||||||
import Signal from "../../models/can/signal";
|
import Signal from "../../models/can/signal";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { shallow, mount, render } from "enzyme";
|
import { shallow, mount, render } from "enzyme";
|
Loading…
Reference in New Issue