diff --git a/src/components/AddSignals.js b/src/components/AddSignals.js index 0ac41a0..dec9c44 100644 --- a/src/components/AddSignals.js +++ b/src/components/AddSignals.js @@ -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( - - {this.byteValueHex(i)} - - ); + rowBits.push({this.byteValueHex(i)}); rows.push({rowBits}); } @@ -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 ( diff --git a/src/components/SignalLegend.js b/src/components/SignalLegend.js index 4228f3d..d2e7140 100644 --- a/src/components/SignalLegend.js +++ b/src/components/SignalLegend.js @@ -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); diff --git a/src/components/SignalLegend/signalLegend.scss b/src/components/SignalLegend/signalLegend.scss index 81a410f..09beb54 100644 --- a/src/components/SignalLegend/signalLegend.scss +++ b/src/components/SignalLegend/signalLegend.scss @@ -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; diff --git a/src/components/SignalLegendEntry.js b/src/components/SignalLegendEntry.js deleted file mode 100644 index f4889d1..0000000 --- a/src/components/SignalLegendEntry.js +++ /dev/null @@ -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 ( -
- - {valueCol} -
- ); - } - - 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 = ( - { - this.updateField(fieldSpec, e.target.value); - }} - /> - ); - } else { - let value = this.props.signal[field]; - valueCol = {value}; - } - return this.renderField(field, title, valueCol, signal); - } - - renderStringField(fieldSpec, signal) { - const { field, title } = fieldSpec; - let valueCol; - if (this.props.isExpanded) { - valueCol = ( - { - this.updateField(fieldSpec, e.target.value); - }} - /> - ); - } else { - valueCol = {this.props.signal[field]}; - } - - 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 => ( - - )); - valueCol = ( - - ); - } else { - valueCol = {valueOptions[this.props.signal[field]]}; - } - - 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 ( -
- {SignalLegendEntry.fields.map(field => { - return ( -
- {this.renderFieldNode(field, signal)} -
- ); - })} -
- -
-
- ); - } - - 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 ( -
this.props.onSignalHover(signal)} - onMouseLeave={() => this.props.onSignalHoverEnd(signal)} - > -
-
-
- {signal.name} -
-
- -
-
-
- {this.props.isExpanded ? this.renderSignalForm(signal) : null} -
-
- ); - } -} diff --git a/src/components/SignalLegendEntry/ColorBar.js b/src/components/SignalLegendEntry/ColorBar.js new file mode 100644 index 0000000..aae9daa --- /dev/null +++ b/src/components/SignalLegendEntry/ColorBar.js @@ -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(",")}); +`; diff --git a/src/components/SignalLegendEntry/FIELDS.js b/src/components/SignalLegendEntry/FIELDS.js new file mode 100644 index 0000000..457084e --- /dev/null +++ b/src/components/SignalLegendEntry/FIELDS.js @@ -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" + } +]; diff --git a/src/components/SignalLegendEntry/Field.js b/src/components/SignalLegendEntry/Field.js new file mode 100644 index 0000000..68351fd --- /dev/null +++ b/src/components/SignalLegendEntry/Field.js @@ -0,0 +1,13 @@ +import React, { Component } from "react"; + +export default class Field extends Component { + render() { + const { title, htmlFor, children } = this.props; + return ( +
+ + {children} +
+ ); + } +} diff --git a/src/components/SignalLegendEntry/SignalForm.js b/src/components/SignalLegendEntry/SignalForm.js new file mode 100644 index 0000000..d663e14 --- /dev/null +++ b/src/components/SignalLegendEntry/SignalForm.js @@ -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 ( +
+ {FIELDS.map(field => { + // console.log(field, getSignalEdited(field.field)) + return ( +
+ +
+ ); + })} +
+ +
+
+ ); +}; + +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 = ( + { + updateField(fieldSpec, e.target.value); + }} + /> + ); + } else { + let value = signal[field]; + valueCol = {value}; + } + return ( + + {valueCol} + + ); +}; + +const StringField = ({ + fieldSpec, + signal, + isExpanded, + signalEdited, + updateField +}) => { + const { field, title } = fieldSpec; + const htmlFor = `${signal.name}_${field}`; + let valueCol; + if (isExpanded) { + valueCol = ( + { + updateField(fieldSpec, e.target.value); + }} + /> + ); + } else { + valueCol = {signal[field]}; + } + + return ( + + {valueCol} + + ); +}; + +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 => ( + + )); + valueCol = ( + + ); + } else { + valueCol = {valueOptions[signal[field]]}; + } + + return ( + + {valueCol} + + ); +}; + +const FieldNode = ({ field, signal, isExpanded, signalEdited, update }) => { + switch (field.type) { + case "number": + return ( + + ); + case "option": + return ( + + ); + case "string": + return ( + + ); + default: + return undefined; + } +}; diff --git a/src/components/SignalLegendEntry/index.js b/src/components/SignalLegendEntry/index.js new file mode 100644 index 0000000..ec1fe55 --- /dev/null +++ b/src/components/SignalLegendEntry/index.js @@ -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 ( +
this.props.onSignalHover(signal)} + onMouseLeave={() => this.props.onSignalHoverEnd(signal)} + > + +
+
+ {signal.name} +
+
+ +
+
+
+ {isExpanded && ( + + )} +
+
+ ); + } +}