cabana: edit message modal; separate available / selected messages

main
Andy Haden 2017-06-19 20:40:07 -07:00
parent e2ac4cc236
commit 78e146d41b
10 changed files with 234 additions and 37 deletions

View File

@ -14,6 +14,7 @@ const CanFetcher = require('./workers/can-fetcher.worker.js');
const MessageParser = require("./workers/message-parser.worker.js");
const CanOffsetFinder = require('./workers/can-offset-finder.worker.js');
import debounce from './utils/debounce';
import EditMessageModal from './components/EditMessageModal';
export default class CanExplorer extends Component {
static propTypes = {
@ -33,6 +34,8 @@ export default class CanExplorer extends Component {
currentParts: [0,0],
showLoadDbc: false,
showSaveDbc: false,
showEditMessageModal: false,
editMessageModalMessage: null,
dbc: null,
dbcFilename: null,
dbcLastSaved: null
@ -42,11 +45,13 @@ export default class CanExplorer extends Component {
this.hideLoadDbc = this.hideLoadDbc.bind(this);
this.showSaveDbc = this.showSaveDbc.bind(this);
this.hideSaveDbc = this.hideSaveDbc.bind(this);
this.showEditMessageModal = this.showEditMessageModal.bind(this);
this.hideEditMessageModal = this.hideEditMessageModal.bind(this);
this.onDbcSelected = this.onDbcSelected.bind(this);
this.onDbcSaved = this.onDbcSaved.bind(this);
this.onConfirmedSignalChange = this.onConfirmedSignalChange.bind(this);
this.onPartChange = this.onPartChange.bind(this);
this.onMessageEdited = this.onMessageEdited.bind(this);
}
componentWillMount() {
@ -198,6 +203,22 @@ export default class CanExplorer extends Component {
});
}, 500);
showEditMessageModal(msgKey) {
this.setState({showEditMessageModal: true,
editMessageModalMessage: msgKey});
}
hideEditMessageModal() {
this.setState({showEditMessageModal: false});
}
onMessageEdited(messageFrame) {
const message = this.state.messages[this.state.editMessageModalMessage];
message.frame = messageFrame;
this.setState({messages: this.state.messages});
this.hideEditMessageModal();
}
render() {
return (<div className={css(Styles.root)}>
<Meta url={this.state.route.url}
@ -209,7 +230,8 @@ export default class CanExplorer extends Component {
showSaveDbc={this.showSaveDbc}
dbcFilename={this.state.dbcFilename}
dbcLastSaved={this.state.dbcLastSaved}
onPartChange={this.onPartChange} />
onPartChange={this.onPartChange}
showEditMessageModal={this.showEditMessageModal} />
{Object.keys(this.state.messages).length > 0
&& this.state.selectedMessage ?
<Explorer
@ -229,6 +251,11 @@ export default class CanExplorer extends Component {
sourceDbcFilename={this.state.dbcFilename}
onDbcSaved={this.onDbcSaved}
onCancel={this.hideSaveDbc} /> : null}
{this.state.showEditMessageModal ?
<EditMessageModal
onCancel={this.hideEditMessageModal}
onMessageEdited={this.onMessageEdited}
messageFrame={this.state.messages[this.state.editMessageModalMessage].frame} /> : null}
</div>);
}
}

View File

@ -97,7 +97,7 @@ export default class AddSignals extends Component {
}
componentWillReceiveProps({message}) {
const isNewMessage = message.name != this.props.message.name;
const isNewMessage = message.address != this.props.message.address;
if(isNewMessage) {
const signalStyles = this.updateSignalStyles(message.signals);

View File

@ -80,7 +80,7 @@ export default class CanHistogram extends Component {
data={{binned: this.state.bins.bins}}
onSignalSegment={this.onSignalSegment}
/>
<p className={css(Styles.label)}>{this.props.message.name} per time</p>
<p className={css(Styles.label)}>{this.props.message.frame ? this.props.message.frame.name : this.props.message.id} per time</p>
</div>)
: null}

View File

@ -54,11 +54,14 @@ export default class CanLog extends Component {
|| JSON.stringify(nextState) != JSON.stringify(this.state)
|| (this.props.message !== undefined
&& nextProps.message !== undefined
&& this.props.message.signals
&& nextProps.message.signals
&& !elementWiseEquals(
Object.keys(this.props.message.signals),
Object.keys(nextProps.message.signals)));
&&
(
(this.props.message.signals
&& nextProps.message.signals
&& JSON.stringify(this.props.message.signals) != JSON.stringify(nextProps.message.signals))
||
(JSON.stringify(this.props.message.frame) != JSON.stringify(nextProps.message.frame))
));
return shouldUpdate;
}
@ -143,7 +146,7 @@ export default class CanLog extends Component {
</div>
<div className={css(Styles.col,
Styles.messageCol)}>
{this.props.message.name || this.props.message.id}
{(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id}
</div>
<div className={css(Styles.col,
Styles.messageCol,

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { css, StyleSheet } from 'aphrodite/no-important';
import PropTypes from 'prop-types';
import Modal from './Modal';
import Frame from '../models/can/frame';
import {copyOmittingKey} from '../utils/object';
export default class EditMessageModal extends Component {
static propTypes = {
onCancel: PropTypes.func.isRequired,
onMessageEdited: PropTypes.func.isRequired,
messageFrame: PropTypes.instanceOf(Frame).isRequired
};
constructor(props) {
super(props);
this.state = {
messageFrame: Object.assign(Object.create(props.messageFrame), props.messageFrame)
}
this.onContinue = this.onContinue.bind(this);
}
onContinue() {
this.props.onMessageEdited(this.state.messageFrame);
}
render() {
return (<Modal title={"Edit Message " + this.state.messageFrame.id}
continueEnabled={true}
onCancel={this.props.onCancel}
onContinue={this.onContinue}>
<div>
<div>
<p>Name</p>
<input type="text"
value={this.state.messageFrame.name}
onChange={(e) => {
const {messageFrame} = this.state;
messageFrame.name = e.target.value;
this.setState({messageFrame});
}} />
<p>Size</p>
<input type="number"
value={this.state.messageFrame.size}
onChange={(e) => {
const {messageFrame} = this.state;
messageFrame.size = parseInt(e.target.value);
this.setState({messageFrame});
}} />
</div>
</div>
</Modal>);
}
}

View File

@ -351,7 +351,7 @@ export default class Explorer extends Component {
return <CanGraph key={messageId + '_' + signalName}
unplot={() => {this.onSignalUnplotPressed(messageId, signalName)}}
messageName={msg.name}
messageName={msg.frame ? msg.frame.name : null}
signalSpec={msg.signals[signalName]}
onSegmentChanged={this.onSegmentChanged}
segment={this.state.segment}

View File

@ -17,7 +17,8 @@ export default class Meta extends Component {
showLoadDbc: PropTypes.func,
showSaveDbc: PropTypes.func,
dbcFilename: PropTypes.string,
dbcLastSaved: PropTypes.object // moment.js object
dbcLastSaved: PropTypes.object, // moment.js object,
showEditMessageModal: PropTypes.func
};
constructor(props) {
@ -25,7 +26,9 @@ export default class Meta extends Component {
const {dbcLastSaved} = props;
this.state = {
filterText: 'Filter',
lastSaved: dbcLastSaved !== null ? this.props.dbcLastSaved.fromNow() : null
lastSaved: dbcLastSaved !== null ? this.props.dbcLastSaved.fromNow() : null,
selectedMessages: [],
hoveredMessages: []
};
this.onFilterChanged = this.onFilterChanged.bind(this);
this.onFilterFocus = this.onFilterFocus.bind(this);
@ -48,6 +51,13 @@ export default class Meta extends Component {
if(nextProps.lastSaved !== this.props.lastSaved && typeof nextProps === 'object') {
this.setState({lastSaved: nextProps.dbcLastSaved.fromNow()})
}
const nextMsgKeys = Object.keys(nextProps.messages);
if(JSON.stringify(nextMsgKeys) != JSON.stringify(Object.keys(this.props.messages))) {
let {selectedMessages} = this.state;
selectedMessages = selectedMessages.filter((m) => nextMsgKeys.indexOf(m) !== -1);
this.setState({selectedMessages, hoveredMessages: []});
}
}
onFilterChanged(e) {
@ -62,7 +72,8 @@ export default class Meta extends Component {
msgKeyFilter(key) {
const {filterText} = this.state;
const msgName = this.props.messages[key].name || '';
const msg = this.props.messages[key];
const msgName = (msg.frame ? msg.frame.name : '');
return (filterText == 'Filter'
|| filterText == ''
@ -75,6 +86,101 @@ export default class Meta extends Component {
return dbcLastSaved.fromNow();
}
onMessageHover(key) {
let {hoveredMessages} = this.state;
if(hoveredMessages.indexOf(key) !== -1) return;
hoveredMessages.push(key);
this.setState({hoveredMessages});
}
onMessageHoverEnd(key) {
let {hoveredMessages} = this.state;
hoveredMessages = hoveredMessages.filter((m) => m != key);
this.setState({hoveredMessages});
}
onMsgEditClick(key) {
this.props.showEditMessageModal(key);
}
onMsgRemoveClick(key) {
let {selectedMessages} = this.state;
selectedMessages = selectedMessages.filter((m) => m != key);
this.setState({selectedMessages});
}
hoverButtons(key) {
return ([<div className={css(Styles.hoverButton, Styles.editButton)}
onClick={() => this.onMsgEditClick(key)}>
<p>Edit</p>
</div>,
<div className={css(Styles.hoverButton, Styles.removeButton)}
onClick={() => this.onRemoveSelectedMsg(key)}>
<p>Remove</p>
</div>]);
}
selectedMessagesList() {
const {selectedMessages, hoveredMessages} = this.state;
if(selectedMessages.length === 0) return null;
const messages = selectedMessages
.sort()
.map((key) => {
const msg = this.props.messages[key];
return <li key={key}
className={css(Styles.message,
Styles.selectedMessage)}
onMouseEnter={() => this.onMessageHover(key)}
onMouseLeave={() => this.onMessageHoverEnd(key)}>
{msg.frame ? msg.frame.name : ''} ({key})
{hoveredMessages.indexOf(key) !== -1 ? this.hoverButtons(key): null}
</li>
});
return (<div>
<p>Selected Messages</p>
<ul className={css(Styles.messageList)}>
{messages}
</ul>
</div>);
}
onMessageSelected(key) {
// uncomment when we support multiple messages
// const selectedMessages = this.state.selectedMessages.filter((m) => m != key);
const selectedMessages = [];
selectedMessages.push(key);
this.setState({selectedMessages});
this.props.onMessageSelected(key);
}
availableMessagesList() {
if(Object.keys(this.props.messages).length === 0) {
return null;
}
return (<div>
<p>Available Messages</p>
<input type="text"
defaultValue="Filter"
value={this.state.filterText}
onFocus={this.onFilterFocus}
onChange={this.onFilterChanged} />
<ul className={css(Styles.messageList)}>
{Object.keys(this.props.messages)
.filter(this.msgKeyFilter)
.sort()
.map((key) => {
const msg = this.props.messages[key];
return <li onClick={() => {this.onMessageSelected(key)}}
key={key}
className={css(Styles.message)}>{msg.frame ? msg.frame.name : ''} ({key})</li>
})}
</ul>
</div>);
}
render() {
return (
<div className={css(Styles.root)}>
@ -115,23 +221,8 @@ export default class Meta extends Component {
onPartChange={this.props.onPartChange}
partsCount={this.props.partsCount}
/>
<div>
<input type="text"
defaultValue="Filter"
value={this.state.filterText}
onFocus={this.onFilterFocus}
onChange={this.onFilterChanged} />
<ul className={css(Styles.messageList)}>
{Object.keys(this.props.messages)
.filter(this.msgKeyFilter)
.sort()
.map((key) => (
<li onClick={() => {this.props.onMessageSelected(key)}}
key={key}
className={css(Styles.message)}>{this.props.messages[key].name} ({key})</li>
))}
</ul>
</div>
{this.selectedMessagesList()}
{this.availableMessagesList()}
</div>
);
}
@ -164,7 +255,9 @@ const Styles = StyleSheet.create({
backgroundColor: 'rgba(0,0,0,0.1)'
},
marginTop: 5,
fontSize: 14
fontSize: 14,
display: 'flex',
flexDirection: 'row'
},
messageList: {
margin: 0,
@ -176,5 +269,22 @@ const Styles = StyleSheet.create({
textDecoration: 'underline'
},
display: 'inline'
},
hoverButton: {
height: 15,
padding: 8,
borderRadius: 4,
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
marginLeft: 15
},
editButton: {
backgroundColor: 'RGBA(105, 69, 33, 1.00)',
color: 'RGBA(251, 253, 242, 1.00)'
},
removeButton: {
backgroundColor: 'RGBA(255, 34, 59, 0.83)',
color: 'RGBA(251, 253, 242, 1.00)'
}
});

View File

@ -65,7 +65,9 @@ export default class DBC {
const messageNames = [];
for(let msg of this.messages.values()) {
messageNames.push(msg.name);
if(msg.frame) {
messageNames.push(msg.frame.name);
}
}
let msgNum = 1, msgName;
@ -129,7 +131,7 @@ export default class DBC {
getMessageName(msgId) {
const msg = this.messages.get(msgId);
if(msg) return msg.name;
if(msg && msg.frame) return msg.frame.name;
return null;
}

View File

@ -3,4 +3,4 @@ export function swapKeysAndValues(obj, f) {
acc[obj[k]] = k;
return acc
},{})
};
};

View File

@ -6,8 +6,7 @@ import * as CanApi from '../api/can';
const Int64LE = require('int64-buffer').Int64LE
function createMessageSpec(dbc, address, id, bus) {
return {name: dbc.getMessageName(address),
address: address,
return {address: address,
id: id,
bus: bus,
entries: [],