Designing Cabana v1.1.2 (#83)
* developing v1.1 design with scrollable sticky columns * add font-awesome fonts, import styles * remove and ignore compiled src css * styling legend entries, packet list, light meta styling * refactor and style signal packet list, add signal id to field labels, re-add remove signal, re-add part selectormain
parent
349f03fecf
commit
03daabad0b
|
@ -4,6 +4,8 @@
|
|||
/node_modules
|
||||
|
||||
# stylesheets
|
||||
/src/*.css
|
||||
/src/*.css.map
|
||||
/src/**/**/*.css
|
||||
/src/**/**/*.css.map
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ export default class CanExplorer extends Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
messages: {},
|
||||
selectedMessages: [],
|
||||
route: {},
|
||||
canFrameOffset: -1,
|
||||
firstCanTime: 0,
|
||||
|
@ -75,6 +76,7 @@ export default class CanExplorer extends Component {
|
|||
this.onMessageSelected = this.onMessageSelected.bind(this);
|
||||
this.onMessageUnselected = this.onMessageUnselected.bind(this);
|
||||
this.initCanData = this.initCanData.bind(this);
|
||||
this.updateSelectedMessages = this.updateSelectedMessages.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -342,80 +344,102 @@ export default class CanExplorer extends Component {
|
|||
this.setState({seekTime, seekIndex, selectedMessage: msgKey});
|
||||
}
|
||||
|
||||
updateSelectedMessages(selectedMessages) {
|
||||
this.setState({selectedMessages});
|
||||
}
|
||||
|
||||
onMessageUnselected(msgKey) {
|
||||
this.setState({selectedMessage: null});
|
||||
}
|
||||
|
||||
loginWithGithub() {
|
||||
return <a href={GithubAuth.authorizeUrl(this.state.route.fullname || '')}>Log in with Github</a>
|
||||
return (
|
||||
<a href={GithubAuth.authorizeUrl(this.state.route.fullname || '')}>
|
||||
<i className='fa fa-github'></i>
|
||||
<span> Log in with Github</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div>
|
||||
{this.state.isLoading ?
|
||||
<LoadingBar
|
||||
isLoading={this.state.isLoading}
|
||||
/> : null}
|
||||
<div className='cabana'>
|
||||
<Meta url={this.state.route.url}
|
||||
messages={this.state.messages}
|
||||
currentParts={this.state.currentParts}
|
||||
partsCount={this.state.route.proclog || 0}
|
||||
onMessageSelected={this.onMessageSelected}
|
||||
onMessageUnselected={this.onMessageUnselected}
|
||||
showLoadDbc={this.showLoadDbc}
|
||||
showSaveDbc={this.showSaveDbc}
|
||||
dbcFilename={this.state.dbcFilename}
|
||||
dbcLastSaved={this.state.dbcLastSaved}
|
||||
onPartChange={this.onPartChange}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
dongleId={this.props.dongleId}
|
||||
name={this.props.name}
|
||||
route={this.state.route}
|
||||
seekTime={this.state.seekTime}
|
||||
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
||||
githubAuthToken={this.props.githubAuthToken}
|
||||
loginWithGithub={this.loginWithGithub()}
|
||||
isDemo={this.props.isDemo}
|
||||
/>
|
||||
{this.state.route.url ?
|
||||
<Explorer
|
||||
url={this.state.route.url}
|
||||
messages={this.state.messages}
|
||||
selectedMessage={this.state.selectedMessage}
|
||||
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
||||
onSeek={this.onSeek}
|
||||
onUserSeek={this.onUserSeek}
|
||||
canFrameOffset={this.state.canFrameOffset}
|
||||
firstCanTime={this.state.firstCanTime}
|
||||
seekTime={this.state.seekTime}
|
||||
seekIndex={this.state.seekIndex}
|
||||
currentParts={this.state.currentParts}
|
||||
partsLoaded={this.state.partsLoaded}
|
||||
autoplay={this.props.autoplay}
|
||||
/>
|
||||
: null}
|
||||
return (
|
||||
<div id="cabana">
|
||||
{this.state.isLoading ?
|
||||
<LoadingBar
|
||||
isLoading={this.state.isLoading}
|
||||
/> : null}
|
||||
<div className='cabana-header'>
|
||||
<a className='cabana-header-logo' href=''>Comma Cabana</a>
|
||||
<div className='cabana-header-account'>
|
||||
{this.props.githubAuthToken ?
|
||||
<p className={css(Styles.githubAuth)}>GitHub Authenticated</p>
|
||||
: this.loginWithGithub()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='cabana-window'>
|
||||
<Meta url={this.state.route.url}
|
||||
messages={this.state.messages}
|
||||
selectedMessages={this.state.selectedMessages}
|
||||
updateSelectedMessages={this.updateSelectedMessages}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
currentParts={this.state.currentParts}
|
||||
onMessageSelected={this.onMessageSelected}
|
||||
onMessageUnselected={this.onMessageUnselected}
|
||||
showLoadDbc={this.showLoadDbc}
|
||||
showSaveDbc={this.showSaveDbc}
|
||||
dbcFilename={this.state.dbcFilename}
|
||||
dbcLastSaved={this.state.dbcLastSaved}
|
||||
dongleId={this.props.dongleId}
|
||||
name={this.props.name}
|
||||
route={this.state.route}
|
||||
seekTime={this.state.seekTime}
|
||||
maxByteStateChangeCount={this.state.maxByteStateChangeCount}
|
||||
isDemo={this.props.isDemo}
|
||||
/>
|
||||
{this.state.route.url ?
|
||||
<Explorer
|
||||
url={this.state.route.url}
|
||||
messages={this.state.messages}
|
||||
selectedMessage={this.state.selectedMessage}
|
||||
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
||||
onSeek={this.onSeek}
|
||||
onUserSeek={this.onUserSeek}
|
||||
canFrameOffset={this.state.canFrameOffset}
|
||||
firstCanTime={this.state.firstCanTime}
|
||||
seekTime={this.state.seekTime}
|
||||
seekIndex={this.state.seekIndex}
|
||||
currentParts={this.state.currentParts}
|
||||
partsLoaded={this.state.partsLoaded}
|
||||
autoplay={this.props.autoplay}
|
||||
showEditMessageModal={this.showEditMessageModal}
|
||||
onPartChange={this.onPartChange}
|
||||
route={this.state.route}
|
||||
partsCount={this.state.route.proclog || 0}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{this.state.showLoadDbc ? <LoadDbcModal
|
||||
onDbcSelected={this.onDbcSelected}
|
||||
onCancel={this.hideLoadDbc}
|
||||
openDbcClient={this.openDbcClient}
|
||||
loginWithGithub={this.loginWithGithub()}
|
||||
/> : null}
|
||||
{this.state.showSaveDbc ? <SaveDbcModal
|
||||
dbc={this.state.dbc}
|
||||
sourceDbcFilename={this.state.dbcFilename}
|
||||
onDbcSaved={this.onDbcSaved}
|
||||
onCancel={this.hideSaveDbc}
|
||||
openDbcClient={this.openDbcClient}
|
||||
hasGithubAuth={this.props.githubAuthToken !== null}
|
||||
loginWithGithub={this.loginWithGithub()} /> : null}
|
||||
{this.state.showEditMessageModal ?
|
||||
<EditMessageModal
|
||||
onCancel={this.hideEditMessageModal}
|
||||
onMessageFrameEdited={this.onMessageFrameEdited}
|
||||
message={this.state.messages[this.state.editMessageModalMessage]} /> : null}
|
||||
</div>);
|
||||
{this.state.showLoadDbc ? <LoadDbcModal
|
||||
onDbcSelected={this.onDbcSelected}
|
||||
onCancel={this.hideLoadDbc}
|
||||
openDbcClient={this.openDbcClient}
|
||||
loginWithGithub={this.loginWithGithub()}
|
||||
/> : null}
|
||||
{this.state.showSaveDbc ? <SaveDbcModal
|
||||
dbc={this.state.dbc}
|
||||
sourceDbcFilename={this.state.dbcFilename}
|
||||
onDbcSaved={this.onDbcSaved}
|
||||
onCancel={this.hideSaveDbc}
|
||||
openDbcClient={this.openDbcClient}
|
||||
hasGithubAuth={this.props.githubAuthToken !== null}
|
||||
loginWithGithub={this.loginWithGithub()} /> : null}
|
||||
{this.state.showEditMessageModal ?
|
||||
<EditMessageModal
|
||||
onCancel={this.hideEditMessageModal}
|
||||
onMessageFrameEdited={this.onMessageFrameEdited}
|
||||
message={this.state.messages[this.state.editMessageModalMessage]} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import ReactList from 'react-list';
|
||||
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import cx from 'classnames';
|
||||
import { formatMsgDec, formatMsgHex } from '../models/can-msg-fmt';
|
||||
import { elementWiseEquals } from '../utils/array';
|
||||
import Images from '../styles/images';
|
||||
|
@ -29,14 +30,16 @@ export default class CanLog extends Component {
|
|||
length: 0,
|
||||
expandedMessages: [],
|
||||
messageHeights: [],
|
||||
expandAllChecked: false
|
||||
allPacketsExpanded: false
|
||||
}
|
||||
|
||||
this.messageRow = this.messageRow.bind(this);
|
||||
this.renderLogListItemMessage = this.renderLogListItemMessage.bind(this);
|
||||
this.addDisplayedMessages = this.addDisplayedMessages.bind(this);
|
||||
this.renderMessage = this.renderMessage.bind(this);
|
||||
this.renderTable = this.renderTable.bind(this);
|
||||
this.renderLogListItem = this.renderLogListItem.bind(this);
|
||||
this.renderLogList = this.renderLogList.bind(this);
|
||||
this.onExpandAllChanged = this.onExpandAllChanged.bind(this);
|
||||
this.toggleExpandAllPackets = this.toggleExpandAllPackets.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -97,110 +100,113 @@ export default class CanLog extends Component {
|
|||
} else return value;
|
||||
}
|
||||
|
||||
expandedMessage(msg) {
|
||||
isMessageExpanded(msg) {
|
||||
return this.state.expandedMessages.indexOf(msg.time) !== -1;
|
||||
}
|
||||
|
||||
toggleSignalPlot(msg, name, plotted) {
|
||||
if (!plotted) {
|
||||
this.props.onSignalPlotPressed(msg, name);
|
||||
} else {
|
||||
this.props.onSignalUnplotPressed(msg, name);
|
||||
}
|
||||
}
|
||||
|
||||
toggleExpandPacketSignals(msg) {
|
||||
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msg);
|
||||
const msgHasSignals = Object.keys(msg.signals).length > 0;
|
||||
if (msgIsExpanded && msgHasSignals) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages
|
||||
.filter((expMsgTime) => expMsgTime !== msg.time)})
|
||||
} else if (msgHasSignals) {
|
||||
this.setState({expandedMessages: this.state.expandedMessages.concat([msg.time])})
|
||||
this.props.onMessageExpanded();
|
||||
} else { return; }
|
||||
}
|
||||
|
||||
renderLogListItemSignals(msg) {
|
||||
const { message } = this.props;
|
||||
return (
|
||||
<div className='signal-log' key={msg.time + '-expanded'}>
|
||||
<div className={css(Styles.col)}>
|
||||
<div className={css(Styles.signalCol)}>
|
||||
<table className={css(Styles.signalTable)}>
|
||||
<tbody>
|
||||
{Object.entries(msg.signals).map(([name, value]) => {
|
||||
return [name, value, this.isSignalPlotted(this.props.message.id, name)]
|
||||
}).map(([name, value, isPlotted]) => {
|
||||
const signal = msg.signals[name];
|
||||
const {unit} = signal;
|
||||
return (<tr key={name}>
|
||||
<td>{name}</td>
|
||||
<td><p className={css(Styles.signalValue)}>{this.signalValuePretty(signal, value)} {unit}</p></td>
|
||||
{isPlotted ?
|
||||
<td className={css(Styles.pointerUnderlineHover)}
|
||||
onClick={() => {this.props.onSignalUnplotPressed(this.props.message.id, name)}}>[unplot]</td>
|
||||
:
|
||||
<td className={css(Styles.pointerUnderlineHover)}
|
||||
onClick={() => {this.props.onSignalPlotPressed(this.props.message.id, name)}}>[plot]</td>
|
||||
}
|
||||
</tr>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='signals-log-list-signals'>
|
||||
{ Object.entries(msg.signals).map(([name, value]) => {
|
||||
return [name, value, this.isSignalPlotted(message.id, name)]
|
||||
}).map(([name, value, isPlotted]) => {
|
||||
const signal = msg.signals[name];
|
||||
const plottedButtonClass = isPlotted ? null : 'button--alpha';
|
||||
const plottedButtonText = isPlotted ? 'Hide Plot' : 'Show Plot';
|
||||
const { unit } = signal;
|
||||
return (
|
||||
<div key={ name } className='signals-log-list-signal'>
|
||||
<div className='signals-log-list-signal-message'>
|
||||
<span>{ name }</span>
|
||||
</div>
|
||||
<div className='signals-log-list-signal-value'>
|
||||
<span>{ this.signalValuePretty(signal, value) } { unit }</span>
|
||||
</div>
|
||||
<div className='signals-log-list-signal-action'
|
||||
onClick={ () => { this.toggleSignalPlot(this.props.message.id, name, isPlotted) } }>
|
||||
<button className={ cx('button--tiny', plottedButtonClass) }>
|
||||
<span>{ plottedButtonText }</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
isMessageExpanded(msg) {
|
||||
return this.state.expandedMessages.indexOf(msg.time) !== -1;
|
||||
renderLogListItemMessage(msg, key) {
|
||||
const msgIsExpanded = this.state.allPacketsExpanded || this.isMessageExpanded(msg);
|
||||
const msgHasSignals = Object.keys(msg.signals).length > 0;
|
||||
const hasSignalsClass = msgHasSignals ? 'has-signals' : null;
|
||||
const expandedClass = msgIsExpanded ? 'is-expanded' : null;
|
||||
const row = (
|
||||
<div key={key} className={cx('signals-log-list-item', hasSignalsClass, expandedClass)}>
|
||||
<div className='signals-log-list-item-header'
|
||||
onClick={ () => { this.toggleExpandPacketSignals(msg) } }>
|
||||
<div className='signals-log-list-time'>
|
||||
<span>{msg.relTime.toFixed(3)}</span>
|
||||
</div>
|
||||
<div className='signals-log-list-message'>
|
||||
<span>{(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id}</span>
|
||||
</div>
|
||||
<div className='signals-log-list-bytes'>
|
||||
<span>{msg.hexData}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='signals-log-list-item-body'>
|
||||
{ msgIsExpanded ? this.renderLogListItemSignals(msg) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
messageRow(msg, key) {
|
||||
const msgIsExpanded = this.state.expandAllChecked || this.isMessageExpanded(msg);
|
||||
const hasSignals = Object.keys(msg.signals).length > 0;
|
||||
const rowStyle = (hasSignals ? Styles.pointer : null);
|
||||
const row = [<div key={key}
|
||||
className={css(Styles.row, Styles.messageRow, rowStyle)}
|
||||
onClick={() => {
|
||||
if(!hasSignals) return;
|
||||
if(msgIsExpanded) {
|
||||
this.collapseMessage(msg);
|
||||
} else {
|
||||
this.expandMessage(msg);
|
||||
}
|
||||
}}>
|
||||
{hasSignals ?
|
||||
(msgIsExpanded ? <div className={css(Styles.col, Styles.arrowCell)}>{<Images.downArrow styles={[Styles.arrow]} />}</div>
|
||||
:
|
||||
<div className={css(Styles.col, Styles.arrowCell)}>{<Images.rightArrow styles={[Styles.arrow]} />}</div>
|
||||
)
|
||||
: <div className={css(Styles.col)}></div>
|
||||
}
|
||||
<div className={css(Styles.col, Styles.timefieldCol)}>
|
||||
{msg.relTime.toFixed(3)}
|
||||
</div>
|
||||
<div className={css(Styles.col,
|
||||
Styles.messageCol)}>
|
||||
{(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id}
|
||||
</div>
|
||||
<div className={css(Styles.col,
|
||||
Styles.messageCol,
|
||||
Styles.hex)}>
|
||||
{msg.hexData}
|
||||
</div>
|
||||
</div>];
|
||||
renderLogListItem(index, key) {
|
||||
let offset = this.props.messageIndex;
|
||||
if(offset === 0 && this.props.segmentIndices.length === 2) {
|
||||
offset = this.props.segmentIndices[0];
|
||||
}
|
||||
|
||||
if(msgIsExpanded) {
|
||||
row.push(this.expandedMessage(msg));
|
||||
}
|
||||
|
||||
return row;
|
||||
return this.renderLogListItemMessage(this.props.message.entries[offset + index], key);
|
||||
}
|
||||
|
||||
renderMessage(index, key) {
|
||||
let offset = this.props.messageIndex;
|
||||
if(offset === 0 && this.props.segmentIndices.length === 2) {
|
||||
offset = this.props.segmentIndices[0];
|
||||
}
|
||||
|
||||
return this.messageRow(this.props.message.entries[offset + index], key);
|
||||
}
|
||||
|
||||
renderTable(items, ref) {
|
||||
return (<div className={css(Styles.root)}>
|
||||
<div className={css(Styles.row)}>
|
||||
<div className={css(Styles.col, Styles.dropdownCol)}> </div>
|
||||
<div className={css(Styles.col, Styles.timefieldCol)}>Time (s)</div>
|
||||
<div className={css(Styles.col)}>
|
||||
Message
|
||||
</div>
|
||||
<div className={css(Styles.col)}>
|
||||
Bytes
|
||||
</div>
|
||||
renderLogList(items, ref) {
|
||||
return (
|
||||
<div className='signals-log-list'>
|
||||
<div className='signals-log-list-header'>
|
||||
<div className='signals-log-list-time'>Time</div>
|
||||
<div className='signals-log-list-message'>Message</div>
|
||||
<div className='signals-log-list-bytes'>Bytes</div>
|
||||
</div>
|
||||
<div className={css(Styles.tableRowGroup)}
|
||||
<div className='signals-log-list-items'
|
||||
ref={ref}>
|
||||
{items}
|
||||
{items}
|
||||
</div>
|
||||
</div>)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
listLength() {
|
||||
|
@ -218,25 +224,36 @@ export default class CanLog extends Component {
|
|||
}
|
||||
|
||||
onExpandAllChanged(e) {
|
||||
this.setState({expandAllChecked: e.target.checked});
|
||||
this.setState({allPacketsExpanded: e.target.checked});
|
||||
}
|
||||
|
||||
toggleExpandAllPackets() {
|
||||
this.setState({allPacketsExpanded: !this.state.allPacketsExpanded});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return <div className='cabana-explorer-signals-log'>
|
||||
<p>Expand all messages:
|
||||
<input type="checkbox"
|
||||
checked={this.state.expandAllChecked}
|
||||
onChange={this.onExpandAllChanged} />
|
||||
</p>
|
||||
<ReactList
|
||||
itemRenderer={this.renderMessage}
|
||||
itemsRenderer={this.renderTable}
|
||||
length={this.listLength()}
|
||||
pageSize={50}
|
||||
updateWhenThisValueChanges={this.props.messageIndex}
|
||||
type='variable' />
|
||||
</div>;
|
||||
let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All';
|
||||
let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha';
|
||||
return (
|
||||
<div className='cabana-explorer-signals-log'>
|
||||
<div className='cabana-explorer-signals-log-header'>
|
||||
<strong>Message Packets</strong>
|
||||
<button className={cx('button--tiny', expandAllClass)}
|
||||
onClick={this.toggleExpandAllPackets}>
|
||||
{expandAllText}
|
||||
</button>
|
||||
</div>
|
||||
<div className='cabana-explorer-signals-log-body'>
|
||||
<ReactList
|
||||
itemRenderer={this.renderLogListItem}
|
||||
itemsRenderer={this.renderLogList}
|
||||
length={this.listLength()}
|
||||
pageSize={50}
|
||||
updateWhenThisValueChanges={this.props.messageIndex}
|
||||
type='variable' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,23 +5,91 @@
|
|||
|
||||
*/
|
||||
|
||||
@import '../../styles/_global/colors';
|
||||
@import '../../styles/_global/all';
|
||||
|
||||
.cabana {
|
||||
#cabana {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cabana-header {
|
||||
background: $color-grey-90;
|
||||
color: #fff;
|
||||
height: 68px;
|
||||
padding: 22px;
|
||||
&-logo {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
pointer-events: none; // disable actual click
|
||||
}
|
||||
&-account {
|
||||
float: right;
|
||||
a {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cabana-window {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cabana-explorer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
&-header {
|
||||
}
|
||||
&-signals {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 3%;
|
||||
&-header {}
|
||||
&-controller {}
|
||||
max-height: 100vh;
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
&-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 89px;
|
||||
padding: 5%;
|
||||
h6,
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
h6 {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
padding-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
&-context {
|
||||
flex: 4;
|
||||
}
|
||||
&-action {
|
||||
flex: 1;
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-window {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
&-controller {
|
||||
padding: 5%;
|
||||
}
|
||||
&-matrix {
|
||||
table {
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
|
@ -32,19 +100,41 @@
|
|||
}
|
||||
}
|
||||
&-log {
|
||||
background: $color-grey-10;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
border-radius: 5px;
|
||||
padding: 3%;
|
||||
border-top: 1px solid $color-grey-30;
|
||||
padding: 0 5%;
|
||||
&-header {
|
||||
padding: 5% 0;
|
||||
strong {
|
||||
color: $color-grey-70;
|
||||
font-size: 14px;
|
||||
}
|
||||
button {
|
||||
float: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
&-body {
|
||||
background: $color-grey-10;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
&-list {
|
||||
padding: 3%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-visuals {
|
||||
background: $color-grey-10;
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 1.5%;
|
||||
padding-left: 0;
|
||||
padding: 1.5% 3%;
|
||||
&-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.1);
|
||||
margin-bottom: 2%;
|
||||
padding: 2% 0;
|
||||
}
|
||||
&-camera {
|
||||
border: 5px solid #000;
|
||||
border-radius: 5px;
|
||||
|
@ -66,11 +156,13 @@
|
|||
}
|
||||
}
|
||||
&-plots {
|
||||
border: 1px solid $color-grey-30;
|
||||
border: 1px solid $color-grey-40;
|
||||
border-radius: 5px;
|
||||
margin: 3% 0;
|
||||
}
|
||||
&-plot {
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 1.5% 3%;
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
|
|
|
@ -5,15 +5,99 @@
|
|||
|
||||
*/
|
||||
|
||||
@import '../../styles/_global/colors';
|
||||
@import '../../styles/_global/all';
|
||||
|
||||
.cabana-meta {
|
||||
background: $color-grey-20;
|
||||
display: flex;
|
||||
flex: .5;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
&-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.1);
|
||||
flex-direction: row;
|
||||
padding: 22px;
|
||||
@extend %clearfix;
|
||||
&-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
padding-bottom: 1%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
&-filename {
|
||||
display: block;
|
||||
}
|
||||
&-last-saved {
|
||||
color: $color-grey-60;
|
||||
font-size: 13px;
|
||||
margin-bottom: -5px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
&-actions {
|
||||
padding: 18px 0;
|
||||
}
|
||||
&-action {
|
||||
float: left;
|
||||
padding: 0 1%;
|
||||
width: (100%/3);
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-left: 11px;
|
||||
&-header {
|
||||
height: 34px;
|
||||
padding: 22px 11px;
|
||||
}
|
||||
&-window {
|
||||
background: $color-grey-30;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
&-filter {
|
||||
border-bottom: 1px solid rgba(0,0,0,.05);
|
||||
height: 64px;
|
||||
padding: 11px;
|
||||
}
|
||||
&-list {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition-duration: .1s;
|
||||
&:not(.is-selected):hover {
|
||||
background: rgba(0,0,0,.05);
|
||||
}
|
||||
&.is-selected {
|
||||
background: $color-grey-40;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table {
|
||||
border-spacing: 0;
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
thead tr td:first-child,
|
||||
tbody tr td:first-child {
|
||||
padding-left: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ const Styles = StyleSheet.create({
|
|||
position: 'relative'
|
||||
},
|
||||
selectedPart: {
|
||||
backgroundColor: 'black',
|
||||
backgroundColor: '#6f6f6f',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
},
|
||||
|
|
|
@ -5,38 +5,112 @@
|
|||
|
||||
*/
|
||||
|
||||
@import '../../styles/_global/index';
|
||||
@import '../../styles/_global/all';
|
||||
|
||||
.cabana-explorer-signals-legend {
|
||||
background: $color-grey-10;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
border-radius: 5px;
|
||||
margin: 5% 0;
|
||||
padding: 2%;
|
||||
margin-top: 5%;
|
||||
}
|
||||
|
||||
.signals-legend-entry {
|
||||
$entry-header-height: 42px;
|
||||
$entry-header-icon-size: 28px;
|
||||
overflow: auto;
|
||||
transition-duration: .1s;
|
||||
padding: 0 2%;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(0,0,0,.05);
|
||||
}
|
||||
&-header {
|
||||
cursor: pointer;
|
||||
line-height: 42px;
|
||||
&-name {
|
||||
float: left;
|
||||
width: 70%;
|
||||
&:hover {
|
||||
background: rgba(0,0,0,.02);
|
||||
}
|
||||
&.is-expanded {
|
||||
.signals-legend-entry-header {
|
||||
border-bottom-color: rgba(0,0,0,.05);
|
||||
height: $entry-header-height + 3px;
|
||||
&:before {
|
||||
left: 0;
|
||||
padding-top: 5px;
|
||||
top: -3px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
&-plotted {
|
||||
.signals-legend-entry-body {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&-header {
|
||||
border-bottom: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
height: $entry-header-height;
|
||||
line-height: $entry-header-height;
|
||||
position: relative;
|
||||
transition-duration: .2s;
|
||||
&:before {
|
||||
color: $color-grey-80;
|
||||
content: $fa-var-chevron-right;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-family: 'FontAwesome';
|
||||
height: $entry-header-height;
|
||||
left: -2px;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
transition-duration: .1s;
|
||||
text-align: center;
|
||||
width: $entry-header-icon-size;
|
||||
}
|
||||
&-name {
|
||||
color: $color-grey-80;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
padding-left: $entry-header-icon-size;
|
||||
width: 75%;
|
||||
}
|
||||
&-action {
|
||||
float: left;
|
||||
text-align: right;
|
||||
width: 30%;
|
||||
padding-right: 2%;
|
||||
width: 25%;
|
||||
button {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-body {
|
||||
display: none;
|
||||
padding: 2%;
|
||||
}
|
||||
&.is-expanded {
|
||||
|
||||
&-form {
|
||||
&-field {
|
||||
min-height: $input-height;
|
||||
padding: 1px 0;
|
||||
@extend %clearfix;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(0,0,0,.05);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
label {
|
||||
color: $color-grey-80;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: $input-height--small;
|
||||
width: 40%;
|
||||
}
|
||||
input,
|
||||
select {
|
||||
float: left;
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
&-remove {
|
||||
padding: 2% 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import cx from 'classnames';
|
||||
|
||||
import Signal from '../models/can/signal';
|
||||
import DbcUtils from '../utils/dbc';
|
||||
|
@ -150,6 +151,7 @@ export default class SignalLegendEntry extends Component {
|
|||
this.toggleEditing = this.toggleEditing.bind(this);
|
||||
this.updateField = this.updateField.bind(this);
|
||||
this.onNameChange = this.onNameChange.bind(this);
|
||||
this.toggleSignalPlot = this.toggleSignalPlot.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -159,12 +161,16 @@ export default class SignalLegendEntry extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
field(field, title, valueCol) {
|
||||
field(field, title, valueCol, signal) {
|
||||
const value = this.props.signal[field];
|
||||
|
||||
let titleCol = <td>{title}</td>;
|
||||
|
||||
return <tr key={field}>{titleCol}<td>{valueCol}</td></tr>;
|
||||
return (
|
||||
<div key={field} className='form-field form-field--small'>
|
||||
<label htmlFor={`${signal}_${field}`}>{title}</label>
|
||||
{valueCol}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
updateField(fieldSpec, value) {
|
||||
|
@ -187,7 +193,7 @@ export default class SignalLegendEntry extends Component {
|
|||
this.props.onSignalChange(signalCopy, signal);
|
||||
}
|
||||
|
||||
numberField(fieldSpec) {
|
||||
renderNumberField(fieldSpec, signal) {
|
||||
const {field, title, options} = fieldSpec;
|
||||
let valueCol;
|
||||
|
||||
|
@ -197,39 +203,40 @@ export default class SignalLegendEntry extends Component {
|
|||
let num = Number(value);
|
||||
value = (isNaN(num) ? '' : num);
|
||||
}
|
||||
|
||||
valueCol = <input type="number"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
let {value} = e.target;
|
||||
|
||||
this.updateField(fieldSpec, value);
|
||||
}}/>;
|
||||
valueCol = (
|
||||
<input id={`${signal}_${field}`}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={(e) => {this.updateField(fieldSpec, e.target)}
|
||||
}/>
|
||||
);
|
||||
} else {
|
||||
let value = this.props.signal[field];
|
||||
valueCol = <span>{value}</span>;
|
||||
}
|
||||
return this.field(field, title, valueCol);
|
||||
return this.field(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
stringField(fieldSpec) {
|
||||
renderStringField(fieldSpec, signal) {
|
||||
const {field, title} = fieldSpec;
|
||||
let valueCol;
|
||||
if(this.state.isEditing) {
|
||||
valueCol = <input type="text"
|
||||
value={this.state.signalEdited[field] || ''}
|
||||
onChange={(e) => {
|
||||
this.updateField(fieldSpec, e.target.value)
|
||||
}}
|
||||
/>;
|
||||
valueCol = (
|
||||
<input id={`${signal}_${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.field(field, title, valueCol);
|
||||
return this.field(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
optionField(fieldSpec) {
|
||||
renderOptionField(fieldSpec, signal) {
|
||||
let valueCol;
|
||||
const {field, title} = fieldSpec;
|
||||
const {options, optionValues} = fieldSpec.options;
|
||||
|
@ -243,24 +250,20 @@ export default class SignalLegendEntry extends Component {
|
|||
value={optionValues[opt]}>{opt}</option>
|
||||
}
|
||||
);
|
||||
valueCol = <select
|
||||
valueCol = (
|
||||
<select id={`${signal}_${field}`}
|
||||
defaultValue={this.state.signalEdited[field]}
|
||||
onChange={(e) => {
|
||||
this.updateField(fieldSpec, e.target.value === "true")
|
||||
}}>
|
||||
{optionEles}
|
||||
</select>;
|
||||
onChange={
|
||||
(e) => { this.updateField(fieldSpec, e.target.value === "true") }
|
||||
}>
|
||||
{optionEles}
|
||||
</select>
|
||||
);
|
||||
} else {
|
||||
valueCol = <span>{valueOptions[this.props.signal[field]]}</span>;
|
||||
}
|
||||
|
||||
return this.field(field, title, valueCol);
|
||||
}
|
||||
|
||||
removeSignal(signal) {
|
||||
return (<tr>
|
||||
<td onClick={() => {this.props.onSignalRemove(signal)}}>Remove Signal</td>
|
||||
</tr>);
|
||||
return this.field(field, title, valueCol, signal);
|
||||
}
|
||||
|
||||
titleForField(field, signal) {
|
||||
|
@ -271,14 +274,14 @@ export default class SignalLegendEntry extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
fieldNode(field, signal) {
|
||||
renderFieldNode(field, signal) {
|
||||
field.title = this.titleForField(field, signal);
|
||||
if(field.type === 'number') {
|
||||
return this.numberField(field);
|
||||
return this.renderNumberField(field, signal);
|
||||
} else if(field.type === 'option') {
|
||||
return this.optionField(field);
|
||||
return this.renderOptionField(field, signal);
|
||||
} else if(field.type === 'string') {
|
||||
return this.stringField(field);
|
||||
return this.renderStringField(field, signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,8 +307,6 @@ export default class SignalLegendEntry extends Component {
|
|||
this.props.onSignalChange(signalCopy, signal);
|
||||
} else {
|
||||
signalEdited = signalCopy;
|
||||
// Show plot when expanding
|
||||
this.props.onSignalPlotChange(true, signal.name)
|
||||
}
|
||||
|
||||
// Expand and enable signal editing
|
||||
|
@ -320,57 +321,62 @@ export default class SignalLegendEntry extends Component {
|
|||
}
|
||||
|
||||
onNameChange(e) {
|
||||
// this.updateField('name', e.target.value);
|
||||
this.setState({nameEdited: e.target.value})
|
||||
}
|
||||
|
||||
expandedSignal(signal) {
|
||||
renderSignalForm(signal) {
|
||||
const startBitTitle = signal.isLittleEndian ? 'Least significant bit' : 'Most significant bit';
|
||||
return (
|
||||
<table>
|
||||
<tr key={signal.uid + '-expanded'} className=''>
|
||||
<td colSpan="3">
|
||||
<table>
|
||||
<tbody>
|
||||
{SignalLegendEntry.fields.map((field) => this.fieldNode(field, signal))}
|
||||
{this.removeSignal(signal)}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div className='signals-legend-entry-form'>
|
||||
{SignalLegendEntry.fields.map((field) => {
|
||||
return (
|
||||
<div className='signals-legend-entry-form-field'>
|
||||
{this.renderFieldNode(field, signal.name)}
|
||||
</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.name);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isExpanded, isEditing, signalEdited, nameEdited} = this.state;
|
||||
const {signal, highlightedStyle, plottedSignals, isPlotted} = this.props;
|
||||
const expandedEntryClass = this.props.isExpanded ? 'is-expanded' : null;
|
||||
const plottedButtonClass = this.props.isPlotted ? 'button' : 'button--alpha';
|
||||
const plottedButtonText = this.props.isPlotted ? 'Hide Plot' : 'Show Plot';
|
||||
return (
|
||||
<div
|
||||
className="signals-legend-entry"
|
||||
onMouseEnter={() => this.props.onSignalHover(signal)}
|
||||
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}>
|
||||
<div
|
||||
className="signals-legend-entry-header"
|
||||
onClick={this.toggleEditing}>
|
||||
<div className="signals-legend-entry-header-name">
|
||||
<span>{this.props.isExpanded ? '\u2193' : '\u2192'}</span>
|
||||
<strong>{signal.name}</strong>
|
||||
className={cx('signals-legend-entry', expandedEntryClass)}
|
||||
onMouseEnter={() => this.props.onSignalHover(signal)}
|
||||
onMouseLeave={() => this.props.onSignalHoverEnd(signal)}>
|
||||
<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-header-plotted">
|
||||
<span>Plot: </span>
|
||||
<input type="checkbox"
|
||||
checked={isPlotted}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => {
|
||||
this.props.onSignalPlotChange(e.target.checked, signal.name)
|
||||
}}
|
||||
/>
|
||||
<div className="signals-legend-entry-body">
|
||||
{this.props.isExpanded ? this.renderSignalForm(signal) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="signals-legend-entry-body">
|
||||
{this.props.isExpanded ? this.expandedSignal(signal) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,4 +6,91 @@
|
|||
|
||||
*/
|
||||
|
||||
@import '../../styles/_global/index';
|
||||
@import '../../styles/_global/all';
|
||||
|
||||
.signals-log-list {
|
||||
color: $color-grey-80;
|
||||
font-size: 14px;
|
||||
&-time {
|
||||
flex: 1;
|
||||
}
|
||||
&-message {
|
||||
flex: 3;
|
||||
}
|
||||
&-bytes {
|
||||
flex: 2;
|
||||
}
|
||||
&-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.1);
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
padding: 2% 3% 2% 7%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
&-item {
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 1px 0;
|
||||
padding-right: 3%;
|
||||
position: relative;
|
||||
transition-duration: .1s;
|
||||
&:not(.is-expanded):hover {
|
||||
background: rgba(0,0,0,.02);
|
||||
}
|
||||
&:not(.is-expanded):active {
|
||||
padding-top: 0.5%;
|
||||
padding-bottom: 0.5%;
|
||||
}
|
||||
&.has-signals {
|
||||
&:before {
|
||||
color: $color-grey-80;
|
||||
content: $fa-var-chevron-right;
|
||||
display: block;
|
||||
font-family: 'FontAwesome';
|
||||
font-size: 8px;
|
||||
line-height: 26px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
transition-duration: .1s;
|
||||
width: 8%;
|
||||
}
|
||||
}
|
||||
&.is-expanded {
|
||||
background: rgba(0,0,0,.03);
|
||||
border-color: rgba(0,0,0,.05);
|
||||
padding-top: 1%;
|
||||
&:before {
|
||||
margin-top: -3px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
&-header {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 1% 0 1% 7%;
|
||||
}
|
||||
}
|
||||
&-signals {}
|
||||
&-signal {
|
||||
align-items: center;
|
||||
border-top: 1px solid rgba(0,0,0,.05);
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
padding: 1% 0 1% 7%;
|
||||
&-message {
|
||||
flex: 3;
|
||||
}
|
||||
&-value {
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
&-action {
|
||||
flex: 1;
|
||||
button {
|
||||
font-size: 10px;
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, {Component} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import Moment from 'moment';
|
||||
|
||||
import AddSignals from './AddSignals';
|
||||
import CanHistogram from './CanHistogram';
|
||||
|
@ -13,6 +14,7 @@ import Entries from '../models/can/entries';
|
|||
import debounce from '../utils/debounce';
|
||||
import CommonStyles from '../styles/styles';
|
||||
import Images from '../styles/images';
|
||||
import PartSelector from './PartSelector';
|
||||
|
||||
export default class Explorer extends Component {
|
||||
static propTypes = {
|
||||
|
@ -24,6 +26,8 @@ export default class Explorer extends Component {
|
|||
firstCanTime: PropTypes.number,
|
||||
onSeek: PropTypes.func,
|
||||
autoplay: PropTypes.bool,
|
||||
onPartChanged: PropTypes.func,
|
||||
partsCount: PropTypes.number,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -191,6 +195,19 @@ export default class Explorer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
timeWindow() {
|
||||
const {route, currentParts} = this.props;
|
||||
if(route) {
|
||||
const partStartOffset = currentParts[0] * 60,
|
||||
partEndOffset = (currentParts[1] + 1) * 60;
|
||||
|
||||
const windowStartTime = Moment(route.start_time).add(partStartOffset, 's').format('HH:mm:ss');
|
||||
const windowEndTime = Moment(route.start_time).add(partEndOffset, 's').format('HH:mm:ss');
|
||||
|
||||
return `${windowStartTime} - ${windowEndTime}`;
|
||||
} else return '';
|
||||
}
|
||||
|
||||
calcGraphData(msg, signalName) {
|
||||
if(!msg) return null;
|
||||
|
||||
|
@ -426,33 +443,46 @@ export default class Explorer extends Component {
|
|||
}
|
||||
|
||||
renderExplorerSignals() {
|
||||
const selectedMessageKey = this.props.selectedMessage;
|
||||
const selectedMessage = this.props.messages[selectedMessageKey];
|
||||
const selectedMessageName = selectedMessage.frame !== undefined ? selectedMessage.frame.name : 'undefined';
|
||||
return (
|
||||
<div className=''>
|
||||
<div className='cabana-explorer-signals-wrapper'>
|
||||
<div
|
||||
className="cabana-explorer-signals-header"
|
||||
onClick={this.toggleEditSignals}>
|
||||
<span>Edit Signals</span>
|
||||
className='cabana-explorer-signals-header'
|
||||
onClick={this.toggleEditSignals}>
|
||||
<div className='cabana-explorer-signals-header-context'>
|
||||
<h6>Selected Message:</h6>
|
||||
<h3>{selectedMessageName}</h3>
|
||||
</div>
|
||||
<div className='cabana-explorer-signals-header-action'>
|
||||
<button
|
||||
className='button--small'
|
||||
onClick={() => this.props.showEditMessageModal(selectedMessageKey)}>Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='cabana-explorer-signals-window'>
|
||||
{this.state.shouldShowAddSignal ?
|
||||
<AddSignals
|
||||
onConfirmedSignalChange={this.props.onConfirmedSignalChange}
|
||||
message={this.props.messages[this.props.selectedMessage]}
|
||||
onClose={() => {this.setState({shouldShowAddSignal: false})}}
|
||||
messageIndex={this.props.seekIndex}
|
||||
onSignalPlotChange={this.onSignalPlotChanged}
|
||||
plottedSignals={this.state.plottedSignals.filter(
|
||||
({messageId, signalName}) => messageId === this.props.selectedMessage
|
||||
).map(({messageId, signalName}) => signalName)
|
||||
}
|
||||
/> : null}
|
||||
<CanLog message={this.props.messages[this.props.selectedMessage]}
|
||||
messageIndex={this.props.seekIndex}
|
||||
segmentIndices={this.state.segmentIndices}
|
||||
plottedSignals={this.state.plottedSignals}
|
||||
onSignalPlotPressed={this.onSignalPlotPressed}
|
||||
onSignalUnplotPressed={this.onSignalUnplotPressed}
|
||||
showAddSignal={this.showAddSignal}
|
||||
onMessageExpanded={this.onPause} />
|
||||
</div>
|
||||
{this.state.shouldShowAddSignal ?
|
||||
<AddSignals
|
||||
onConfirmedSignalChange={this.props.onConfirmedSignalChange}
|
||||
message={this.props.messages[this.props.selectedMessage]}
|
||||
onClose={() => {this.setState({shouldShowAddSignal: false})}}
|
||||
messageIndex={this.props.seekIndex}
|
||||
onSignalPlotChange={this.onSignalPlotChanged}
|
||||
plottedSignals={this.state.plottedSignals.filter(
|
||||
({messageId, signalName}) => messageId === this.props.selectedMessage
|
||||
).map(({messageId, signalName}) => signalName)
|
||||
}
|
||||
/> : null}
|
||||
<CanLog message={this.props.messages[this.props.selectedMessage]}
|
||||
messageIndex={this.props.seekIndex}
|
||||
segmentIndices={this.state.segmentIndices}
|
||||
plottedSignals={this.state.plottedSignals}
|
||||
onSignalPlotPressed={this.onSignalPlotPressed}
|
||||
onSignalUnplotPressed={this.onSignalUnplotPressed}
|
||||
showAddSignal={this.showAddSignal}
|
||||
onMessageExpanded={this.onPause} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -464,7 +494,7 @@ export default class Explorer extends Component {
|
|||
<CanGraph key={messageId + '_' + signalName}
|
||||
unplot={() => {this.onSignalUnplotPressed(messageId, signalName)}}
|
||||
messageId={messageId}
|
||||
messageName={msg.frame ? msg.frame.name : null}
|
||||
message={msg.frame ? msg.frame.name : null}
|
||||
signalSpec={Object.assign(Object.create(msg.signals[signalName]), msg.signals[signalName])}
|
||||
onSegmentChanged={this.onSegmentChanged}
|
||||
segment={this.state.segment}
|
||||
|
@ -483,6 +513,13 @@ export default class Explorer extends Component {
|
|||
: this.selectMessagePrompt()}
|
||||
</div>
|
||||
<div className='cabana-explorer-visuals'>
|
||||
<div className='cabana-explorer-visuals-header'>
|
||||
{this.timeWindow()}
|
||||
<PartSelector
|
||||
onPartChange={this.props.onPartChange}
|
||||
partsCount={this.props.partsCount}
|
||||
/>
|
||||
</div>
|
||||
<RouteVideoSync
|
||||
message={this.props.messages[this.props.selectedMessage]}
|
||||
secondsLoaded={this.secondsLoaded()}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React, {Component} from 'react';
|
||||
import { StyleSheet, css } from 'aphrodite/no-important';
|
||||
import cx from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Moment from 'moment';
|
||||
import Clipboard from 'clipboard';
|
||||
|
||||
import {modifyQueryParameters} from '../utils/url';
|
||||
import PartSelector from './PartSelector';
|
||||
import LoadDbcModal from './LoadDbcModal';
|
||||
import * as GithubAuth from '../api/github-auth';
|
||||
import Images from '../styles/images';
|
||||
|
@ -19,8 +18,7 @@ export default class Meta extends Component {
|
|||
dongleId: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
messages: PropTypes.objectOf(PropTypes.object),
|
||||
onPartChanged: PropTypes.func,
|
||||
partsCount: PropTypes.number,
|
||||
selectedMessages: PropTypes.array,
|
||||
showLoadDbc: PropTypes.func,
|
||||
showSaveDbc: PropTypes.func,
|
||||
dbcFilename: PropTypes.string,
|
||||
|
@ -40,7 +38,6 @@ export default class Meta extends Component {
|
|||
this.state = {
|
||||
filterText: 'Filter',
|
||||
lastSaved: dbcLastSaved !== null ? this.props.dbcLastSaved.fromNow() : null,
|
||||
selectedMessages: [],
|
||||
hoveredMessages: []
|
||||
};
|
||||
this.onFilterChanged = this.onFilterChanged.bind(this);
|
||||
|
@ -68,9 +65,9 @@ export default class Meta extends Component {
|
|||
|
||||
const nextMsgKeys = Object.keys(nextProps.messages);
|
||||
if(JSON.stringify(nextMsgKeys) != JSON.stringify(Object.keys(this.props.messages))) {
|
||||
let {selectedMessages} = this.state;
|
||||
let {selectedMessages} = this.props;
|
||||
selectedMessages = selectedMessages.filter((m) => nextMsgKeys.indexOf(m) !== -1);
|
||||
this.setState({selectedMessages, hoveredMessages: []});
|
||||
this.setState({hoveredMessages: []});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,10 +120,6 @@ export default class Meta extends Component {
|
|||
this.setState({hoveredMessages});
|
||||
}
|
||||
|
||||
onMsgEditClick(key) {
|
||||
this.props.showEditMessageModal(key);
|
||||
}
|
||||
|
||||
onMsgRemoveClick(key) {
|
||||
let {selectedMessages} = this.state;
|
||||
selectedMessages = selectedMessages.filter((m) => m != key);
|
||||
|
@ -134,50 +127,12 @@ export default class Meta extends Component {
|
|||
this.setState({selectedMessages});
|
||||
}
|
||||
|
||||
hoverButtons(key) {
|
||||
return ([<div key={"edit"}
|
||||
className={css(Styles.hoverButton, Styles.editButton)}
|
||||
onClick={() => this.onMsgEditClick(key)}>
|
||||
<p>Edit</p>
|
||||
</div>,
|
||||
<div key={"remove"}
|
||||
className={css(Styles.hoverButton, Styles.removeButton)}
|
||||
onClick={() => this.onMsgRemoveClick(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 className={css(Styles.messagesList)}>
|
||||
<p>Selected Message</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.updateSelectedMessages(selectedMessages);
|
||||
this.props.onMessageSelected(key);
|
||||
}
|
||||
|
||||
|
@ -223,75 +178,45 @@ export default class Meta extends Component {
|
|||
}
|
||||
|
||||
selectedMessageClass(messageId) {
|
||||
return (this.state.selectedMessages.includes(messageId) ? Styles.messageIsSelected : null);
|
||||
return (this.props.selectedMessages.includes(messageId) ? 'is-selected' : null);
|
||||
}
|
||||
|
||||
availableMessagesList() {
|
||||
renderAvailableMessagesList() {
|
||||
if(Object.keys(this.props.messages).length === 0) {
|
||||
return null;
|
||||
return <p>Loading messages...</p>;
|
||||
}
|
||||
|
||||
const defaultTextVisible = this.state.filterText.trim() === 'Filter';
|
||||
|
||||
return (<div className={css(Styles.messagesList)}>
|
||||
|
||||
<p>Available Messages</p>
|
||||
<div className={css(Styles.filter)}>
|
||||
<input type="text"
|
||||
value={this.state.filterText}
|
||||
onFocus={this.onFilterFocus}
|
||||
onBlur={this.onFilterUnfocus}
|
||||
onChange={this.onFilterChanged}
|
||||
className={css(defaultTextVisible ? Styles.defaultFilterText: null)}
|
||||
/>
|
||||
{this.state.filterText.trim().length > 0 && this.state.filterText !== 'Filter' ?
|
||||
<Images.clear onClick={() => this.setState({filterText: 'Filter'})} />
|
||||
: null}
|
||||
</div>
|
||||
<table className={css(Styles.messageTable)}>
|
||||
<thead>
|
||||
<tr className={css(Styles.messageHeader)}>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Signals</td>
|
||||
<td>Count</td>
|
||||
<td>Bytes</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.orderedMessages()
|
||||
.map((msg) => {
|
||||
return <tr onClick={() => {this.onMessageSelected(msg.id)}}
|
||||
key={msg.id}
|
||||
className={css(Styles.message, this.selectedMessageClass(msg.id))}>
|
||||
<td>{msg.frame ? msg.frame.name : ''}</td>
|
||||
<td>{msg.id}</td>
|
||||
<td>{Object.keys(msg.signals).length}</td>
|
||||
<td>{msg.entries.length}</td>
|
||||
<td>
|
||||
<MessageBytes
|
||||
message={msg}
|
||||
seekTime={this.props.seekTime}
|
||||
maxByteStateChangeCount={this.props.maxByteStateChangeCount} />
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>);
|
||||
}
|
||||
|
||||
timeWindow() {
|
||||
const {route, currentParts} = this.props;
|
||||
if(route) {
|
||||
const partStartOffset = currentParts[0] * 60,
|
||||
partEndOffset = (currentParts[1] + 1) * 60;
|
||||
|
||||
const windowStartTime = Moment(route.start_time).add(partStartOffset, 's').format('HH:mm:ss');
|
||||
const windowEndTime = Moment(route.start_time).add(partEndOffset, 's').format('HH:mm:ss');
|
||||
|
||||
return `${windowStartTime} - ${windowEndTime}`;
|
||||
} else return '';
|
||||
return (
|
||||
<table cellPadding='5'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>ID</td>
|
||||
<td>Count</td>
|
||||
<td>Bytes</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.orderedMessages()
|
||||
.map((msg) => {
|
||||
return (
|
||||
<tr onClick={() => {this.onMessageSelected(msg.id)}}
|
||||
key={msg.id}
|
||||
className={cx('cabana-meta-messages-list-item', this.selectedMessageClass(msg.id))}>
|
||||
<td>{msg.frame ? msg.frame.name : 'undefined'}</td>
|
||||
<td>{msg.id}</td>
|
||||
<td>{msg.entries.length}</td>
|
||||
<td>
|
||||
<MessageBytes
|
||||
message={msg}
|
||||
seekTime={this.props.seekTime}
|
||||
maxByteStateChangeCount={this.props.maxByteStateChangeCount} />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
shareUrl() {
|
||||
|
@ -303,182 +228,53 @@ export default class Meta extends Component {
|
|||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="cabana-meta">
|
||||
{this.props.isDemo ?
|
||||
<div className={css(Styles.chffrPanda)}>
|
||||
<Images.panda styles={[Styles.panda]} />
|
||||
<div className={css(Styles.chffrPandaDesc)}>
|
||||
<p>Data collected with chffr + panda</p>
|
||||
<a href="http://panda.comma.ai" className={css(Styles.chffrPandaGet)}>buy panda</a>
|
||||
<a href="http://chffr.comma.ai" className={css(Styles.chffrPandaGet)}>get chffr</a>
|
||||
</div>
|
||||
</div> : null}
|
||||
<div className={css(Styles.scrollContainer)}>
|
||||
<div>
|
||||
<span className={css(Styles.titleText)}>
|
||||
comma cabana
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{this.props.githubAuthToken ?
|
||||
<p className={css(Styles.githubAuth)}>GitHub Authenticated</p>
|
||||
:
|
||||
this.props.loginWithGithub
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<p className={css(Styles.loadDbc)}
|
||||
onClick={this.props.showLoadDbc}>Load DBC</p>
|
||||
/
|
||||
<p className={css(Styles.loadDbc)}
|
||||
onClick={this.props.showSaveDbc}>Save DBC</p>
|
||||
{this.props.dbcLastSaved !== null ?
|
||||
<div className='cabana-meta'>
|
||||
<div className='cabana-meta-header'>
|
||||
<span className='cabana-meta-header-label'>Currently editing:</span>
|
||||
<strong className='cabana-meta-header-filename'>{this.props.dbcFilename}</strong>
|
||||
{this.props.dbcLastSaved !== null ?
|
||||
<div className='cabana-meta-header-last-saved'>
|
||||
<p>Last saved: {this.lastSavedPretty()}</p>
|
||||
: null
|
||||
}
|
||||
{this.props.dbcFilename ? <p>Editing: {this.props.dbcFilename}</p>: null}
|
||||
<p data-clipboard-text={this.shareUrl()}
|
||||
data-clipboard-action="copy"
|
||||
ref={(ref) => ref ? new Clipboard(ref) : null}>
|
||||
<a href={this.shareUrl()}
|
||||
className={css(Styles.copyShareLink)}
|
||||
onClick={(e) => e.preventDefault()}>Copy share link</a></p>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className='cabana-meta-header-actions'>
|
||||
<div className='cabana-meta-header-action'>
|
||||
<button onClick={this.props.showLoadDbc}>Load DBC</button>
|
||||
</div>
|
||||
<div className='cabana-meta-header-action'
|
||||
data-clipboard-text={this.shareUrl()}
|
||||
data-clipboard-action='copy'
|
||||
ref={(ref) => ref ? new Clipboard(ref) : null}>
|
||||
<a className='button'
|
||||
href={this.shareUrl()}
|
||||
onClick={(e) => e.preventDefault()}>Copy Share Link</a>
|
||||
</div>
|
||||
<div className='cabana-meta-header-action'>
|
||||
<button onClick={this.props.showSaveDbc}>Save DBC</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className={css(Styles.timeWindow)}>{this.timeWindow()}</p>
|
||||
</div>
|
||||
<div className='cabana-meta-messages'>
|
||||
<div className='cabana-meta-messages-header'>
|
||||
<p>Available messages</p>
|
||||
</div>
|
||||
<div className='cabana-meta-messages-window'>
|
||||
<div className='cabana-meta-messages-filter'>
|
||||
<div className='form-field form-field--small'>
|
||||
<input type="text"
|
||||
value={this.state.filterText}
|
||||
onFocus={this.onFilterFocus}
|
||||
onBlur={this.onFilterUnfocus}
|
||||
onChange={this.onFilterChanged} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='cabana-meta-messages-list'>
|
||||
{this.renderAvailableMessagesList()}
|
||||
</div>
|
||||
</div>
|
||||
<PartSelector
|
||||
onPartChange={this.props.onPartChange}
|
||||
partsCount={this.props.partsCount}
|
||||
/>
|
||||
{this.selectedMessagesList()}
|
||||
{this.availableMessagesList()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
chffrPanda: {
|
||||
minWidth: 450,
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderBottom: '1px solid rgba(0,0,0,0.8)',
|
||||
borderRight: '1px solid rgba(0,0,0,0.8)',
|
||||
backgroundColor: 'white',
|
||||
padding: 10
|
||||
},
|
||||
chffrPandaDesc: {
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 10
|
||||
},
|
||||
chffrPandaGet: {
|
||||
display: 'block',
|
||||
fontWeight: 'bold',
|
||||
textDecoration: 'none',
|
||||
':hover': {
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
color: 'rgba(0,0,0,0.8)'
|
||||
},
|
||||
panda: {
|
||||
width: 150,
|
||||
height: 138
|
||||
},
|
||||
scrollContainer: {
|
||||
display: 'block',
|
||||
height: '100%',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
padding: 10,
|
||||
},
|
||||
githubAuth: {
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
},
|
||||
titleText: {
|
||||
fontFamily: 'monospace',
|
||||
paddingRight: 10,
|
||||
fontSize: 24
|
||||
},
|
||||
routeMeta: {
|
||||
borderBottomWidth: '1px',
|
||||
borderColor: 'grey',
|
||||
'*': {
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
message: {
|
||||
cursor: 'pointer',
|
||||
':hover' : {
|
||||
backgroundColor: 'rgba(0,0,0,0.1)'
|
||||
},
|
||||
marginTop: 5,
|
||||
fontSize: 12,
|
||||
},
|
||||
selectedMessage: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
messageIsSelected: {
|
||||
backgroundColor: 'blue',
|
||||
color: 'rgb(233, 233, 233)',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
messageList: {
|
||||
margin: 0,
|
||||
padding: 0
|
||||
},
|
||||
loadDbc: {
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
display: 'inline',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
timeWindow: {
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
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)'
|
||||
},
|
||||
defaultFilterText: {
|
||||
color: 'rgb(205,205,205)'
|
||||
},
|
||||
filter: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: 24
|
||||
},
|
||||
messagesList: {
|
||||
marginTop: 10
|
||||
},
|
||||
messageHeader: {
|
||||
fontSize: 12
|
||||
},
|
||||
copyShareLink: {
|
||||
color: 'black'
|
||||
}
|
||||
});
|
||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
268
src/index.css
268
src/index.css
|
@ -1,268 +0,0 @@
|
|||
/*
|
||||
|
||||
Comma Cabana Styles
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
html, body {
|
||||
min-width: 1340px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: sans-serif; }
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
* {
|
||||
box-sizing: border-box; }
|
||||
|
||||
.g-container {
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
@media only screen and (min-width: 540px) {
|
||||
.g-container {
|
||||
width: 80%; } }
|
||||
@media only screen and (min-width: 960px) {
|
||||
.g-container {
|
||||
width: 75%; } }
|
||||
|
||||
.g-row {
|
||||
position: relative;
|
||||
width: 100%; }
|
||||
|
||||
.g-row::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both; }
|
||||
|
||||
[class*="g-col"] {
|
||||
float: left;
|
||||
min-height: 0.125rem; }
|
||||
|
||||
.g-col-1,
|
||||
.g-col-2,
|
||||
.g-col-3,
|
||||
.g-col-4,
|
||||
.g-col-5,
|
||||
.g-col-6,
|
||||
.g-col-7,
|
||||
.g-col-8,
|
||||
.g-col-9,
|
||||
.g-col-10,
|
||||
.g-col-11,
|
||||
.g-col-12 {
|
||||
width: 100%; }
|
||||
|
||||
@media only screen and (min-width: 720px) {
|
||||
.g-col-1 {
|
||||
width: 8.3333333333%; }
|
||||
|
||||
.g-col-2 {
|
||||
width: 16.6666666667%; }
|
||||
|
||||
.g-col-3 {
|
||||
width: 25%; }
|
||||
|
||||
.g-col-4 {
|
||||
width: 33.3333333333%; }
|
||||
|
||||
.g-col-5 {
|
||||
width: 41.6666666667%; }
|
||||
|
||||
.g-col-6 {
|
||||
width: 50%; }
|
||||
|
||||
.g-col-7 {
|
||||
width: 58.3333333333%; }
|
||||
|
||||
.g-col-8 {
|
||||
width: 66.6666666667%; }
|
||||
|
||||
.g-col-9 {
|
||||
width: 75%; }
|
||||
|
||||
.g-col-10 {
|
||||
width: 83.3333333333%; }
|
||||
|
||||
.g-col-11 {
|
||||
width: 91.6666666667%; }
|
||||
|
||||
.g-col-12 {
|
||||
width: 100%; }
|
||||
|
||||
.hidden-sm {
|
||||
display: block; } }
|
||||
/*
|
||||
|
||||
VG Tooltip Library
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
.vg-tooltip {
|
||||
visibility: hidden;
|
||||
padding: 6px;
|
||||
border-radius: 3px;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
/* The default look of the tooltip is the same as .light-theme
|
||||
but it can be overwritten by .dark-theme */
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid #d9d9d9;
|
||||
color: black; }
|
||||
|
||||
.vg-tooltip td.key, .vg-tooltip td.value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
|
||||
.vg-tooltip td.key {
|
||||
max-width: 150px;
|
||||
text-align: right;
|
||||
padding-right: 1px;
|
||||
font-weight: bold; }
|
||||
|
||||
.vg-tooltip td.value {
|
||||
max-width: 200px;
|
||||
text-align: left; }
|
||||
|
||||
/* Dark and light color themes */
|
||||
.vg-tooltip.dark-theme {
|
||||
background-color: rgba(64, 64, 64, 0.9);
|
||||
color: rgba(255, 255, 255, 0.95); }
|
||||
|
||||
/*
|
||||
|
||||
Meta Component Styles
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
.cabana-meta {
|
||||
background: #e5e5e5;
|
||||
flex: .5;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow-y: scroll; }
|
||||
.cabana-meta table {
|
||||
width: 100%; }
|
||||
|
||||
/*
|
||||
|
||||
Explorer Component Styles
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
.cabana {
|
||||
display: flex; }
|
||||
|
||||
.cabana-explorer {
|
||||
display: flex;
|
||||
flex: 1; }
|
||||
.cabana-explorer-signals {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 3%; }
|
||||
.cabana-explorer-signals-matrix table {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
margin: 2% 0;
|
||||
width: 100%; }
|
||||
.cabana-explorer-signals-log {
|
||||
background: #fafafa;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
padding: 3%; }
|
||||
.cabana-explorer-visuals {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 1.5%;
|
||||
padding-left: 0; }
|
||||
.cabana-explorer-visuals-camera {
|
||||
border: 5px solid #000;
|
||||
border-radius: 5px;
|
||||
max-height: 480px;
|
||||
max-width: 640px;
|
||||
overflow: hidden;
|
||||
position: relative; }
|
||||
.cabana-explorer-visuals-camera-seeker {
|
||||
background: rgba(233, 233, 233, 0.2);
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 4; }
|
||||
.cabana-explorer-visuals-camera video {
|
||||
cursor: pointer;
|
||||
width: 100%; }
|
||||
.cabana-explorer-visuals-plots {
|
||||
border: 1px solid #d3d3d3;
|
||||
border-radius: 5px;
|
||||
margin: 3% 0; }
|
||||
.cabana-explorer-visuals-plot {
|
||||
padding: 1.5% 3%; }
|
||||
.cabana-explorer-visuals-plot:first-child, .cabana-explorer-visuals-plot:last-child {
|
||||
padding-top: 3%;
|
||||
padding-bottom: 3%; }
|
||||
.cabana-explorer-visuals-plot-message {
|
||||
color: #a2a2a2;
|
||||
font-size: 12px; }
|
||||
.cabana-explorer-visuals-plot-signal strong {
|
||||
padding-right: 5px; }
|
||||
.cabana-explorer-visuals-plot-signal a {
|
||||
color: #a2a2a2;
|
||||
cursor: pointer; }
|
||||
.cabana-explorer-visuals-plot-canvas svg {
|
||||
height: auto;
|
||||
width: 100%; }
|
||||
|
||||
/*
|
||||
|
||||
Signal Legend Component
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
.cabana-explorer-signals-legend {
|
||||
background: #fafafa;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
margin: 5% 0;
|
||||
padding: 2%; }
|
||||
|
||||
.signals-legend-entry {
|
||||
overflow: auto; }
|
||||
.signals-legend-entry:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05); }
|
||||
.signals-legend-entry-header {
|
||||
cursor: pointer;
|
||||
line-height: 42px; }
|
||||
.signals-legend-entry-header-name {
|
||||
float: left;
|
||||
width: 70%; }
|
||||
.signals-legend-entry-header-plotted {
|
||||
float: left;
|
||||
text-align: right;
|
||||
width: 30%; }
|
||||
.signals-legend-entry-body {
|
||||
padding: 2%; }
|
||||
|
||||
/*
|
||||
|
||||
ExplorerSignalLog Component Styles
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Also known as CanLog
|
||||
|
||||
*/
|
||||
|
||||
/*# sourceMappingURL=index.css.map */
|
|
@ -8,9 +8,11 @@
|
|||
// Globals
|
||||
@import './styles/base/base';
|
||||
@import './styles/base/grid';
|
||||
@import './styles/base/forms';
|
||||
|
||||
// Libraries
|
||||
@import './styles/lib/index';
|
||||
@import './styles/lib/font-awesome';
|
||||
|
||||
// Components
|
||||
@import './components/Meta/meta';
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// All Global Variables and Mixins
|
||||
@import 'colors';
|
||||
@import 'mixins';
|
||||
@import '../../../node_modules/font-awesome/scss/variables';
|
|
@ -1,5 +1,10 @@
|
|||
// Cabana Color Variables
|
||||
$color-grey-10: #fafafa;
|
||||
$color-grey-20: #e5e5e5;
|
||||
$color-grey-30: #d3d3d3;
|
||||
$color-grey-20: #ededed;
|
||||
$color-grey-30: #e1e1e1;
|
||||
$color-grey-40: #d4d4d4;
|
||||
$color-grey-50: #a2a2a2;
|
||||
$color-grey-60: #979797;
|
||||
$color-grey-70: #6f6f6f;
|
||||
$color-grey-80: #484848;
|
||||
$color-grey-90: #2d2d2d;
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// Global Cabana Variables
|
||||
|
||||
@import './colors';
|
|
@ -0,0 +1,9 @@
|
|||
// Mixins
|
||||
|
||||
%clearfix {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
html, body {
|
||||
min-width: 1340px;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
|
||||
Base Form Elements
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
|
||||
@import '../../styles/_global/all';
|
||||
|
||||
$input-height: 42px;
|
||||
$input-height--small: 34px;
|
||||
$input-height--tiny: 22px;
|
||||
$input-radius: 3px;
|
||||
$input-background: #fff;
|
||||
$button-background: $color-grey-50;
|
||||
$button-background--alpha: rgba(0,0,0,.02);
|
||||
|
||||
.form-field {
|
||||
&.form-field--small {
|
||||
input,
|
||||
select {
|
||||
font-size: 12px;
|
||||
height: $input-height--small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='number'],
|
||||
select {
|
||||
background: $input-background;
|
||||
border: 1px solid $color-grey-50;
|
||||
border-radius: $input-radius;
|
||||
font-size: 14px;
|
||||
height: $input-height;
|
||||
outline: none;
|
||||
padding: 0 14px;
|
||||
width: 100%;
|
||||
&:focus {
|
||||
border-color: $color-grey-60;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='button'],
|
||||
button,
|
||||
a.button {
|
||||
background: $color-grey-50;
|
||||
border: 1px solid $color-grey-60;
|
||||
border-bottom-width: 3px;
|
||||
border-radius: $input-radius;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: $input-height;
|
||||
line-height: $input-height - 2px;
|
||||
outline: 0;
|
||||
padding: 0 5%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px rgba(0,0,0,.2);
|
||||
&:active {
|
||||
background: $color-grey-60;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
&.button--small {
|
||||
height: $input-height--small;
|
||||
line-height: $input-height--small - 2px;
|
||||
}
|
||||
&.button--tiny {
|
||||
height: $input-height--tiny;
|
||||
font-size: 12px;
|
||||
line-height: $input-height--tiny - 2px;
|
||||
}
|
||||
&.button--alpha {
|
||||
background: $button-background--alpha;
|
||||
border-color: $color-grey-30;
|
||||
border-width: 1px;
|
||||
color: $color-grey-70;
|
||||
text-shadow: none;
|
||||
}
|
||||
&.button--inverted {
|
||||
background: transparent;
|
||||
border-width: 1px;
|
||||
color: $button-background;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
|
||||
Font Awesome Icons
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*/
|
||||
|
||||
$fa-font-path: './fonts';
|
||||
@import '../../../node_modules/font-awesome/scss/font-awesome';
|
Loading…
Reference in New Issue