diff --git a/.gitignore b/.gitignore index dc2d230..49d65be 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /node_modules # stylesheets +/src/*.css +/src/*.css.map /src/**/**/*.css /src/**/**/*.css.map diff --git a/src/CanExplorer.js b/src/CanExplorer.js index cd581df..3837ad3 100644 --- a/src/CanExplorer.js +++ b/src/CanExplorer.js @@ -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 Log in with Github + return ( + + + Log in with Github + + ) } render() { - return (
- {this.state.isLoading ? - : null} -
- - {this.state.route.url ? - - : null} + return ( +
+ {this.state.isLoading ? + : null} +
+ Comma Cabana +
+ {this.props.githubAuthToken ? +

GitHub Authenticated

+ : this.loginWithGithub() + }
+
+
+ + {this.state.route.url ? + + : null} +
- {this.state.showLoadDbc ? : null} - {this.state.showSaveDbc ? : null} - {this.state.showEditMessageModal ? - : null} -
); + {this.state.showLoadDbc ? : null} + {this.state.showSaveDbc ? : null} + {this.state.showEditMessageModal ? + : null} +
+ ); } } diff --git a/src/components/CanLog.js b/src/components/CanLog.js index d30b9fa..c819d8d 100644 --- a/src/components/CanLog.js +++ b/src/components/CanLog.js @@ -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 ( -
-
-
- - - {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 ( - - - {isPlotted ? - - : - - } - ); - })} - -
{name}

{this.signalValuePretty(signal, value)} {unit}

{this.props.onSignalUnplotPressed(this.props.message.id, name)}}>[unplot] {this.props.onSignalPlotPressed(this.props.message.id, name)}}>[plot]
-
+
+ { 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 ( +
+
+ { name } +
+
+ { this.signalValuePretty(signal, value) } { unit } +
+
{ this.toggleSignalPlot(this.props.message.id, name, isPlotted) } }> + +
+
+ ); + })}
-
) } - 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 = ( +
+
{ this.toggleExpandPacketSignals(msg) } }> +
+ {msg.relTime.toFixed(3)} +
+
+ {(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id} +
+
+ {msg.hexData} +
+
+
+ { msgIsExpanded ? this.renderLogListItemSignals(msg) : null} +
+
+ ); + + 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 = [
{ - if(!hasSignals) return; - if(msgIsExpanded) { - this.collapseMessage(msg); - } else { - this.expandMessage(msg); - } - }}> - {hasSignals ? - (msgIsExpanded ?
{}
- : -
{}
- ) - :
- } -
- {msg.relTime.toFixed(3)} -
-
- {(this.props.message.frame ? this.props.message.frame.name : null) || this.props.message.id} -
-
- {msg.hexData} -
-
]; + 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 (
-
-
 
-
Time (s)
-
- Message -
-
- Bytes -
+ renderLogList(items, ref) { + return ( +
+
+
Time
+
Message
+
Bytes
-
- {items} + {items}
-
) +
+ ) } 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
-

Expand all messages: - -

- -
; + let expandAllText = this.state.allPacketsExpanded ? 'Collapse All' : 'Expand All'; + let expandAllClass = this.state.allPacketsExpanded ? null : 'button--alpha'; + return ( +
+
+ Message Packets + +
+
+ +
+
+ ); } } diff --git a/src/components/Explorer/explorer.scss b/src/components/Explorer/explorer.scss index 068144b..24d98a4 100644 --- a/src/components/Explorer/explorer.scss +++ b/src/components/Explorer/explorer.scss @@ -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 { diff --git a/src/components/Meta/meta.scss b/src/components/Meta/meta.scss index 2ff662d..7caeafc 100644 --- a/src/components/Meta/meta.scss +++ b/src/components/Meta/meta.scss @@ -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; + } } } diff --git a/src/components/PartSelector.js b/src/components/PartSelector.js index 6cdddea..2673b07 100644 --- a/src/components/PartSelector.js +++ b/src/components/PartSelector.js @@ -131,7 +131,7 @@ const Styles = StyleSheet.create({ position: 'relative' }, selectedPart: { - backgroundColor: 'black', + backgroundColor: '#6f6f6f', height: '100%', position: 'absolute', }, diff --git a/src/components/SignalLegend/signalLegend.scss b/src/components/SignalLegend/signalLegend.scss index c149137..8b86a8f 100644 --- a/src/components/SignalLegend/signalLegend.scss +++ b/src/components/SignalLegend/signalLegend.scss @@ -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; + } } } diff --git a/src/components/SignalLegendEntry.js b/src/components/SignalLegendEntry.js index 76a6a4a..c304d14 100644 --- a/src/components/SignalLegendEntry.js +++ b/src/components/SignalLegendEntry.js @@ -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 = {title}; - return {titleCol}{valueCol}; + return ( +
+ + {valueCol} +
+ ) } 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 = { - let {value} = e.target; - - this.updateField(fieldSpec, value); - }}/>; + valueCol = ( + {this.updateField(fieldSpec, e.target)} + }/> + ); } else { let value = this.props.signal[field]; valueCol = {value}; } - 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 = { - this.updateField(fieldSpec, e.target.value) - }} - />; + valueCol = ( + { + this.updateField(fieldSpec, e.target.value) + }} + />); } else { valueCol = {this.props.signal[field]}; } - 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} } ); - valueCol = ; + onChange={ + (e) => { this.updateField(fieldSpec, e.target.value === "true") } + }> + {optionEles} + + ); } else { valueCol = {valueOptions[this.props.signal[field]]}; } - return this.field(field, title, valueCol); - } - - removeSignal(signal) { - return ( - {this.props.onSignalRemove(signal)}}>Remove Signal - ); + 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 ( - - - - -
- - - {SignalLegendEntry.fields.map((field) => this.fieldNode(field, signal))} - {this.removeSignal(signal)} - -
-
+
+ {SignalLegendEntry.fields.map((field) => { + return ( +
+ {this.renderFieldNode(field, signal.name)} +
+ ) + })} +
+ +
+
); } + 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 (
this.props.onSignalHover(signal)} - onMouseLeave={() => this.props.onSignalHoverEnd(signal)}> -
-
- {this.props.isExpanded ? '\u2193' : '\u2192'} - {signal.name} + className={cx('signals-legend-entry', expandedEntryClass)} + onMouseEnter={() => this.props.onSignalHover(signal)} + onMouseLeave={() => this.props.onSignalHoverEnd(signal)}> +
+
+ {signal.name} +
+
+ +
-
- Plot: - e.stopPropagation()} - onChange={(e) => { - this.props.onSignalPlotChange(e.target.checked, signal.name) - }} - /> +
+ {this.props.isExpanded ? this.renderSignalForm(signal) : null}
-
-
- {this.props.isExpanded ? this.expandedSignal(signal) : null} -
); } diff --git a/src/components/SignalLog/signalLog.scss b/src/components/SignalLog/signalLog.scss index 76d8498..98fe15d 100644 --- a/src/components/SignalLog/signalLog.scss +++ b/src/components/SignalLog/signalLog.scss @@ -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% + } + } + } +} diff --git a/src/components/explorer.js b/src/components/explorer.js index 64f2b29..cdbe6b3 100644 --- a/src/components/explorer.js +++ b/src/components/explorer.js @@ -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 ( -
+
- Edit Signals + className='cabana-explorer-signals-header' + onClick={this.toggleEditSignals}> +
+
Selected Message:
+

{selectedMessageName}

+
+
+ +
+
+
+ {this.state.shouldShowAddSignal ? + {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} +
- {this.state.shouldShowAddSignal ? - {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} -
) } @@ -464,7 +494,7 @@ export default class Explorer extends Component { {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()}
+
+ {this.timeWindow()} + +
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 ([
this.onMsgEditClick(key)}> -

Edit

-
, -
this.onMsgRemoveClick(key)}> -

Remove

-
]); - } - - 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
  • this.onMessageHover(key)} - onMouseLeave={() => this.onMessageHoverEnd(key)}> - {msg.frame ? msg.frame.name : ''} {key} - {hoveredMessages.indexOf(key) !== -1 ? this.hoverButtons(key): null} -
  • - }); - return (
    -

    Selected Message

    -
      - {messages} -
    -
    ); - } - 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

    Loading messages...

    ; } - - const defaultTextVisible = this.state.filterText.trim() === 'Filter'; - - return (
    - -

    Available Messages

    -
    - - {this.state.filterText.trim().length > 0 && this.state.filterText !== 'Filter' ? - this.setState({filterText: 'Filter'})} /> - : null} -
    - - - - - - - - - - - - {this.orderedMessages() - .map((msg) => { - return {this.onMessageSelected(msg.id)}} - key={msg.id} - className={css(Styles.message, this.selectedMessageClass(msg.id))}> - - - - - - - })} - -
    SignalsCountBytes
    {msg.frame ? msg.frame.name : ''}{msg.id}{Object.keys(msg.signals).length}{msg.entries.length} - -
    -
    ); - } - - 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 ( + + + + + + + + + + + {this.orderedMessages() + .map((msg) => { + return ( + {this.onMessageSelected(msg.id)}} + key={msg.id} + className={cx('cabana-meta-messages-list-item', this.selectedMessageClass(msg.id))}> + + + + + + ) + })} + +
    NameIDCountBytes
    {msg.frame ? msg.frame.name : 'undefined'}{msg.id}{msg.entries.length} + +
    + ); } shareUrl() { @@ -303,182 +228,53 @@ export default class Meta extends Component { } render() { return ( -
    - {this.props.isDemo ? -
    - -
    -

    Data collected with chffr + panda

    - buy panda - get chffr -
    -
    : null} -
    -
    - - comma cabana - -
    -
    - {this.props.githubAuthToken ? -

    GitHub Authenticated

    - : - this.props.loginWithGithub - } -
    -
    -

    Load DBC

    -  /  -

    Save DBC

    - {this.props.dbcLastSaved !== null ? +
    +
    + Currently editing: + {this.props.dbcFilename} + {this.props.dbcLastSaved !== null ? +

    Last saved: {this.lastSavedPretty()}

    - : null - } - {this.props.dbcFilename ?

    Editing: {this.props.dbcFilename}

    : null} -

    ref ? new Clipboard(ref) : null}> - e.preventDefault()}>Copy share link

    +
    + : null + } +
    +
    + +
    +
    ref ? new Clipboard(ref) : null}> + e.preventDefault()}>Copy Share Link +
    +
    + +
    - -
    -

    {this.timeWindow()}

    +
    +
    +
    +

    Available messages

    +
    +
    +
    +
    + +
    +
    +
    + {this.renderAvailableMessagesList()} +
    - - {this.selectedMessagesList()} - {this.availableMessagesList()}
    ); } } - -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' - } -}); diff --git a/src/fonts/FontAwesome.otf b/src/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/src/fonts/FontAwesome.otf differ diff --git a/src/fonts/fontawesome-webfont.eot b/src/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/src/fonts/fontawesome-webfont.eot differ diff --git a/src/fonts/fontawesome-webfont.svg b/src/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/src/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/fonts/fontawesome-webfont.ttf b/src/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/src/fonts/fontawesome-webfont.ttf differ diff --git a/src/fonts/fontawesome-webfont.woff b/src/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/src/fonts/fontawesome-webfont.woff differ diff --git a/src/fonts/fontawesome-webfont.woff2 b/src/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/src/fonts/fontawesome-webfont.woff2 differ diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 58add0d..0000000 --- a/src/index.css +++ /dev/null @@ -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 */ diff --git a/src/index.scss b/src/index.scss index 36cf8bd..da58102 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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'; diff --git a/src/styles/_global/all.scss b/src/styles/_global/all.scss new file mode 100644 index 0000000..fa0a26d --- /dev/null +++ b/src/styles/_global/all.scss @@ -0,0 +1,4 @@ +// All Global Variables and Mixins +@import 'colors'; +@import 'mixins'; +@import '../../../node_modules/font-awesome/scss/variables'; diff --git a/src/styles/_global/colors.scss b/src/styles/_global/colors.scss index 1aa0290..52aea9c 100644 --- a/src/styles/_global/colors.scss +++ b/src/styles/_global/colors.scss @@ -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; diff --git a/src/styles/_global/index.scss b/src/styles/_global/index.scss deleted file mode 100644 index 038968a..0000000 --- a/src/styles/_global/index.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Global Cabana Variables - -@import './colors'; diff --git a/src/styles/_global/mixins.scss b/src/styles/_global/mixins.scss new file mode 100644 index 0000000..5b5531a --- /dev/null +++ b/src/styles/_global/mixins.scss @@ -0,0 +1,9 @@ +// Mixins + +%clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} diff --git a/src/styles/base/base.scss b/src/styles/base/base.scss index bd13a88..8b5e666 100644 --- a/src/styles/base/base.scss +++ b/src/styles/base/base.scss @@ -2,6 +2,7 @@ html, body { min-width: 1340px; margin: 0; + overflow-x: hidden; padding: 0; height: 100%; width: 100%; diff --git a/src/styles/base/forms.scss b/src/styles/base/forms.scss new file mode 100644 index 0000000..3a289fd --- /dev/null +++ b/src/styles/base/forms.scss @@ -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; + } +} diff --git a/src/styles/lib/font-awesome.scss b/src/styles/lib/font-awesome.scss new file mode 100644 index 0000000..165649f --- /dev/null +++ b/src/styles/lib/font-awesome.scss @@ -0,0 +1,9 @@ +/* + + Font Awesome Icons + ~~~~~~~~~~~~~~~~~~ + +*/ + +$fa-font-path: './fonts'; +@import '../../../node_modules/font-awesome/scss/font-awesome';