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 events
main
Chris Biscardi 2018-01-19 16:16:04 -08:00 committed by Chris
parent 48a04b3d49
commit ddeea37287
9 changed files with 513 additions and 455 deletions

View File

@ -68,16 +68,6 @@ export default class AddSignals extends Component {
dragSignal: 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) {
@ -124,11 +114,11 @@ export default class AddSignals extends Component {
return style;
}
updateSignalStyles() {
updateSignalStyles = () => {
const signalStyles = this.calcSignalStyles(this.state.signals);
this.setState({ signalStyles });
}
};
calcSignalStyles(signals) {
const signalStyles = {};
@ -160,11 +150,11 @@ export default class AddSignals extends Component {
})[0];
}
onSignalHover(signal) {
onSignalHover = signal => {
if (!signal) return;
this.setState({ highlightedSignal: signal.name }, this.updateSignalStyles);
}
};
signalBitIndex(bitIdx, signal) {
// todo does this work for both big and little endian?
@ -175,7 +165,7 @@ export default class AddSignals extends Component {
return bitIdx - startBit;
}
onBitHover(bitIdx, signal) {
onBitHover = (bitIdx, signal) => {
let { dragStartBit, signals, dragSignal } = this.state;
if (dragStartBit !== null) {
@ -279,13 +269,13 @@ export default class AddSignals extends Component {
if (signal) {
this.onSignalHover(signal);
}
}
};
onSignalHoverEnd(signal) {
onSignalHoverEnd = signal => {
if (!signal) return;
this.setState({ highlightedSignal: null }, this.updateSignalStyles);
}
};
nextNewSignalName() {
const existingNames = Object.keys(this.state.signals);
@ -440,10 +430,11 @@ export default class AddSignals extends Component {
}
renderBitMatrix() {
const { message } = this.props;
const rows = [];
let rowCount;
if (this.props.message.frame && this.props.message.frame.size) {
rowCount = Math.floor(this.props.message.frame.size * 8 / 8);
if (message.frame && message.frame.size) {
rowCount = Math.floor(message.frame.size * 8 / 8);
} else {
rowCount = 8;
}
@ -483,11 +474,7 @@ export default class AddSignals extends Component {
);
}
rowBits.push(
<td key={"hex-repr"} className={css(Styles.hex)}>
{this.byteValueHex(i)}
</td>
);
rowBits.push(<td key={"hex-repr"}>{this.byteValueHex(i)}</td>);
rows.push(<tr key={i.toString()}>{rowBits}</tr>);
}
@ -500,23 +487,23 @@ export default class AddSignals extends Component {
);
}
resetDragState() {
resetDragState = () => {
this.setState({
dragStartBit: null,
dragSignal: null,
dragCurrentBit: null
});
}
};
onTentativeSignalChange(signal) {
onTentativeSignalChange = signal => {
// Tentative signal changes are not propagated up
// but their effects are displayed in the bitmatrix
const { signals } = this.state;
signals[signal.name] = signal;
this.setState({ signals });
}
};
onSignalChange(signal, oldSignal) {
onSignalChange = (signal, oldSignal) => {
const { signals } = this.state;
for (let signalName in signals) {
@ -527,13 +514,13 @@ export default class AddSignals extends Component {
signals[signal.name] = signal;
this.setState({ signals }, this.propagateUpSignalChange);
}
};
onSignalRemove(signal) {
onSignalRemove = signal => {
const { signals } = this.state;
delete signals[signal.name];
this.setState({ signals }, this.propagateUpSignalChange);
}
};
propagateUpSignalChange() {
const { signals } = this.state;
@ -544,11 +531,11 @@ export default class AddSignals extends Component {
);
}
onSignalPlotChange(shouldPlot, signalUid) {
onSignalPlotChange = (shouldPlot, signalUid) => {
const { message } = this.props;
this.props.onSignalPlotChange(shouldPlot, message.id, signalUid);
}
};
render() {
return (

View File

@ -18,15 +18,11 @@ export default class SignalLegend extends Component {
plottedSignalUids: PropTypes.array
};
constructor(props) {
super(props);
this.state = {
expandedSignals: []
};
this.toggleExpandSignal = this.toggleExpandSignal.bind(this);
}
state = {
expandedSignals: []
};
toggleExpandSignal(s) {
toggleExpandSignal = s => {
const { expandedSignals } = this.state;
if (!expandedSignals.includes(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);
this.setState({ expandedSignals: updatedExpandedSignals });
}
}
};
checkExpandedSignal(s) {
return this.state.expandedSignals.includes(s);

View File

@ -42,19 +42,6 @@
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 {
border-bottom: 1px solid transparent;
cursor: pointer;

View File

@ -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>
);
}
}

View File

@ -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(",")});
`;

View File

@ -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"
}
];

View File

@ -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>
);
}
}

View File

@ -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;
}
};

View File

@ -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>
);
}
}