├── src ├── api │ └── .gitkeep ├── utils │ ├── .gitkeep │ ├── PropTypes.js │ ├── fonts.js │ ├── icons.js │ └── units.js ├── App.css ├── config │ ├── parameters │ │ ├── Text.css │ │ ├── VisibleOn.css │ │ ├── Text.js │ │ ├── ParameterList.js │ │ ├── Enabled.js │ │ ├── Scale.js │ │ ├── SpeedScaleType.js │ │ ├── index.js │ │ ├── Radius.js │ │ ├── VideoMode.js │ │ ├── Units.js │ │ ├── AltitudeScaleType.js │ │ ├── ScaleAlignment.js │ │ ├── FontSize.js │ │ ├── Select.js │ │ ├── HorizontalAlignment.js │ │ ├── VerticalAlignment.js │ │ ├── Position.js │ │ └── VisibleOn.js │ ├── settings │ │ ├── Watt.js │ │ ├── GpsHdop.js │ │ ├── ArmState.js │ │ ├── Gps2Hdop.js │ │ ├── SpeedAir.js │ │ ├── TotalTrip.js │ │ ├── Efficiency.js │ │ ├── FlightMode.js │ │ ├── Gps2Status.js │ │ ├── GpsStatus.js │ │ ├── GpsLatitude.js │ │ ├── SpeedGround.js │ │ ├── WPDistance.js │ │ ├── Gps2Latitude.js │ │ ├── GpsLongitude.js │ │ ├── HomeDistance.js │ │ ├── HomeLatitude.js │ │ ├── BatteryCurrent.js │ │ ├── BatteryVoltage.js │ │ ├── Gps2Longitude.js │ │ ├── HomeLongitude.js │ │ ├── BatteryConsumed.js │ │ ├── AbsoluteAltitude.js │ │ ├── BatteryRemaining.js │ │ ├── RelativeAltitude.js │ │ ├── Startup.js │ │ ├── VarioGraph.js │ │ ├── Wind.js │ │ ├── RCChannels.js │ │ ├── HomeDirection.js │ │ ├── Compass.js │ │ ├── HomeDirectionDebugInfo.js │ │ ├── Time.js │ │ ├── ClimbRate.js │ │ ├── Video.js │ │ ├── AltitudeScale.js │ │ ├── SpeedScale.js │ │ ├── Attitude3d.js │ │ ├── Map.js │ │ ├── ArtificialHorizon.js │ │ ├── Serial.js │ │ ├── Radar.js │ │ ├── SimpleSettings.js │ │ ├── Throttle.js │ │ ├── index.js │ │ ├── LinkQuality.js │ │ ├── Rssi.js │ │ └── Switching.js │ ├── Config.js │ └── reducer.js ├── app.png ├── pixler │ ├── Preview.css │ ├── Editor.css │ ├── Pixel.css │ ├── Preview.js │ ├── Pixel.js │ ├── Pixler.js │ ├── actions.js │ └── Editor.js ├── store │ ├── configureStore.js │ ├── configureStore.production.js │ └── configureStore.development.js ├── data │ └── icons │ │ ├── lookup.js │ │ ├── small.js │ │ └── medium.js ├── components │ ├── Label.js │ ├── Column.js │ └── Input.js ├── preview │ ├── Grid.css │ ├── ArmState.js │ ├── GpsLatitude.js │ ├── GpsLongitude.js │ ├── BatteryCurrent.js │ ├── BatteryVoltage.js │ ├── BatteryConsumed.js │ ├── BatteryRemaining.js │ ├── HomeLatitude.js │ ├── HomeLongitude.js │ ├── actions.js │ ├── GpsHdop.js │ ├── AbsoluteAltitude.js │ ├── RelativeAltitude.js │ ├── Watt.js │ ├── TotalTrip.js │ ├── WpDistance.js │ ├── HomeDistance.js │ ├── SpeedAir.js │ ├── SpeedGround.js │ ├── reducer.js │ ├── Alarms.js │ ├── Grid.js │ ├── Efficiency.js │ ├── LinkQuality.js │ ├── Rssi.js │ ├── GpsStatus.js │ ├── Compass.js │ ├── PreviewBase.js │ ├── Time.js │ ├── VarioGraph.js │ ├── ArtificialHorizon.js │ ├── FlightMode.js │ ├── HomeDirectionDebugInfo.js │ ├── Wind.js │ ├── StringPreview.js │ ├── index.js │ ├── HomeDirection.js │ ├── SpeedScale.js │ ├── AltitudeScale.js │ ├── ClimbRate.js │ ├── Throttle.js │ ├── Radar.js │ └── RCChannels.js ├── routes.js ├── reducers.js ├── DevTools.js ├── index.js ├── App.js ├── eventPage.js └── app.global.css ├── .node-version ├── .eslintignore ├── .gitattributes ├── preview.png ├── app ├── icon_128.png ├── main.html └── manifest.json ├── static └── background.png ├── .csslintrc ├── test ├── .eslintrc ├── preview │ └── reducer.spec.js ├── setup.js ├── util │ └── fonts.spec.js └── pixler │ └── reducer.spec.js ├── .gitignore ├── etc └── dummy.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── sergiovilar.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── xcuserdata │ └── sergiovilar.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ └── xcshareddata │ └── xcschemes │ ├── scheme.xcscheme │ ├── scheme copy.xcscheme │ └── blablabla.xcscheme ├── webpack.config.node.js ├── .editorconfig ├── .babelrc ├── .eslintrc ├── .travis.yml ├── .codeclimate.yml ├── server.js ├── wallaby.js ├── .changelogrc ├── LICENSE ├── README.md └── webpack.config.js /src/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 5.9.0 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .mainContent { 2 | height: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/config/parameters/Text.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 2rem 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/config/parameters/VisibleOn.css: -------------------------------------------------------------------------------- 1 | .base { 2 | padding: 2rem 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasBales/PlayuavOSDConfigurator/HEAD/preview.png -------------------------------------------------------------------------------- /src/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasBales/PlayuavOSDConfigurator/HEAD/src/app.png -------------------------------------------------------------------------------- /app/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasBales/PlayuavOSDConfigurator/HEAD/app/icon_128.png -------------------------------------------------------------------------------- /static/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasBales/PlayuavOSDConfigurator/HEAD/static/background.png -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /src/pixler/Preview.css: -------------------------------------------------------------------------------- 1 | .canvas { 2 | background-color: #CCCCCC; 3 | position: absolute; 4 | left: 300px; 5 | top: 250px; 6 | } 7 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-unused-expressions": 0, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/config/settings/Watt.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class Watt extends SimpleSettings { 4 | label = 'watt'; 5 | name = 'watt'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/GpsHdop.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class GpsHdop extends SimpleSettings { 4 | label = 'gps hdop'; 5 | name = 'gpsHdop'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/ArmState.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class ArmState extends SimpleSettings { 4 | label = 'arm state'; 5 | name = 'armState'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/Gps2Hdop.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class Gps2Hdop extends SimpleSettings { 4 | label = 'gps 2 hdop'; 5 | name = 'gps2Hdop'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/SpeedAir.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class SpeedAir extends SimpleSettings { 4 | label = 'speed air'; 5 | name = 'speedAir'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/TotalTrip.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class TotalTrip extends SimpleSettings { 4 | label = 'total trip'; 5 | name = 'totalTrip'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/Efficiency.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class Efficiency extends SimpleSettings { 4 | label = 'efficiency'; 5 | name = 'efficiency'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/FlightMode.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class FlightMode extends SimpleSettings { 4 | label = 'flight mode'; 5 | name = 'flightMode'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/Gps2Status.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class Gps2Status extends SimpleSettings { 4 | label = 'gps 2 status'; 5 | name = 'gps2Status'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/GpsStatus.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class GpsStatus extends SimpleSettings { 4 | labels = 'gps status'; 5 | name = 'gpsStatus'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/GpsLatitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class GPSLatitude extends SimpleSettings { 4 | label = 'gps latitude'; 5 | name = 'gpsLatitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/SpeedGround.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class SpeedGround extends SimpleSettings { 4 | label = 'speed ground'; 5 | name = 'speedGround'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/WPDistance.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class WPDistance extends SimpleSettings { 4 | label = 'way-point distance'; 5 | name = 'wpDistance' 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/Gps2Latitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class GPS2Latitude extends SimpleSettings { 4 | label = 'gps 2 latitude'; 5 | name = 'gps2Latitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/GpsLongitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class GPSLongitude extends SimpleSettings { 4 | label = 'gps longitude'; 5 | name = 'gpsLongitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/HomeDistance.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class HomeDistance extends SimpleSettings { 4 | label = 'home distance'; 5 | name = 'homeDistance'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/HomeLatitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class HomeLatitude extends SimpleSettings { 4 | label = 'home latitude'; 5 | name = 'homeLatitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.production'); 3 | } else { 4 | module.exports = require('./configureStore.development'); 5 | } 6 | -------------------------------------------------------------------------------- /src/config/settings/BatteryCurrent.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class BatteryCurrent extends SimpleSettings { 4 | label = 'battery current'; 5 | name = 'batteryCurrent'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/BatteryVoltage.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class BatteryVoltage extends SimpleSettings { 4 | label = 'battery voltage' 5 | name = 'batteryVoltage'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/Gps2Longitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class Gps2Longitude extends SimpleSettings { 4 | label = 'gps 2 longitude'; 5 | name = 'gps2Longitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/HomeLongitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class HomeLongitude extends SimpleSettings { 4 | label = 'home longitude'; 5 | name = 'homeLongitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/BatteryConsumed.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class BatteryConsumed extends SimpleSettings { 4 | label = 'battery consumed'; 5 | name = 'batteryConsumed'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/AbsoluteAltitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class AbsoluteAltitude extends SimpleSettings { 4 | label = 'absolute altitude'; 5 | name = 'absoluteAltitude'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/BatteryRemaining.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class BatteryRemaining extends SimpleSettings { 4 | label = 'battery remaining'; 5 | name = 'batteryRemaining'; 6 | } 7 | -------------------------------------------------------------------------------- /src/config/settings/RelativeAltitude.js: -------------------------------------------------------------------------------- 1 | import SimpleSettings from './SimpleSettings'; 2 | 3 | export default class RelativeAltitude extends SimpleSettings { 4 | label = 'relative altitude'; 5 | name = 'relativeAltitude'; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .grunt 3 | .jshintignore 4 | .jshintrc 5 | .lock-wscript 6 | *.log 7 | *.pid 8 | *.seed 9 | app/dist 10 | coverage 11 | default.conf 12 | lib-cov 13 | logs 14 | node_modules 15 | test-results.xml 16 | 0.*.zip 17 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pixler/Editor.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .square { 7 | height: 20px; 8 | width: 20px; 9 | border: 1px transparent; 10 | text-align: center; 11 | line-height: 20px; 12 | } 13 | -------------------------------------------------------------------------------- /src/data/icons/lookup.js: -------------------------------------------------------------------------------- 1 | export const GPS = 0; 2 | export const HDOP = 1; 3 | export const TIME = 2; 4 | export const WP_DISTANCE = 3; 5 | export const TOTAL_TRIP = 4; 6 | export const RSSI = 5; 7 | export const LINK_QUALITY = 6; 8 | export const HOME_DISTANCE = 7; 9 | -------------------------------------------------------------------------------- /src/utils/PropTypes.js: -------------------------------------------------------------------------------- 1 | import ImmutablePropTypes from 'react-immutable-proptypes'; 2 | 3 | const value = (type) => 4 | ImmutablePropTypes.contains({ 5 | value: type, 6 | originalValue: type, 7 | }); 8 | 9 | export default { 10 | value 11 | }; 12 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/project.xcworkspace/xcuserdata/sergiovilar.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobiasBales/PlayuavOSDConfigurator/HEAD/etc/dummy.xcodeproj/project.xcworkspace/xcuserdata/sergiovilar.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /webpack.config.node.js: -------------------------------------------------------------------------------- 1 | // for babel-plugin-webpack-loaders 2 | const devConfigs = require('./webpack.config'); 3 | 4 | module.exports = { 5 | output: { 6 | libraryTarget: 'commonjs2' 7 | }, 8 | module: { 9 | loaders: devConfigs.module.loaders // remove babel-loader 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/Label.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | export default function Label(props) { 4 | const { text } = props; 5 | return ( 6 | 7 | ); 8 | } 9 | 10 | Label.propTypes = { 11 | text: PropTypes.string.isRequired, 12 | }; 13 | -------------------------------------------------------------------------------- /app/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pixler/Pixel.css: -------------------------------------------------------------------------------- 1 | .pixel { 2 | height: 20px; 3 | width: 20px; 4 | border: 1px solid #AAAAAA; 5 | cursor: pointer; 6 | } 7 | 8 | .empty { 9 | background-color: #CCCCCC; 10 | 11 | } 12 | 13 | .outline { 14 | background-color: #000000; 15 | } 16 | 17 | .shape { 18 | background-color: #FFFFFF; 19 | } 20 | -------------------------------------------------------------------------------- /src/preview/Grid.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .row { 10 | height: 10px; 11 | border-bottom: 1px solid #999999; 12 | } 13 | .column { 14 | width: 10px; 15 | border-right: 1px solid #999999; 16 | display: inline-block; 17 | height: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{json,js,jsx,html,css}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [.eslintrc] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /src/preview/ArmState.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class ArmState extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | armState: PropTypes.number.isRequired, 8 | } 9 | content() { 10 | return this.props.armState ? 'ARMED' : 'DISARMED'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './App'; 4 | import Config from './config/Config'; 5 | import Pixler from './pixler/Pixler'; 6 | 7 | export default ( 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["add-module-exports", ["extensible-destructuring", {"mode": "optout"}]], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | }, 8 | "test": { 9 | "plugins": [ 10 | ["webpack-loaders", { "config": "webpack.config.node.js", "verbose": false }] 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer as routing } from 'react-router-redux'; 3 | import parameters from './config/reducer'; 4 | import pixler from './pixler/reducer'; 5 | import preview from './preview/reducer'; 6 | 7 | const rootReducer = combineReducers({ 8 | parameters, 9 | pixler, 10 | preview, 11 | routing 12 | }); 13 | 14 | export default rootReducer; 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | }, 7 | "globals": { 8 | "chrome": false, 9 | "VERSION": true 10 | }, 11 | "rules": { 12 | "arrow-body-style": 0, 13 | "comma-dangle": 0, 14 | "consistent-return": 0, 15 | "no-use-before-define": 0, 16 | "react/jsx-no-bind": 0 17 | }, 18 | "plugins": [ 19 | "react" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | export default createDevTools( 7 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/preview/GpsLatitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class GpsLatitude extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | gpsLatitude: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { gpsLatitude } = this.props; 12 | return (gpsLatitude / 10000000).toFixed(5); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/preview/reducer.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import preview from '../../src/preview/reducer'; 3 | import { ALARM } from '../../src/preview/actions'; 4 | 5 | describe('reducers', () => { 6 | describe('preview', () => { 7 | it('should handle ALARM', () => { 8 | const state = preview(undefined, { type: ALARM, payload: 3 }); 9 | expect(state.get('alarm')).to.equal(3); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/preview/GpsLongitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class GpsLongitude extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | gpsLongitude: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { gpsLongitude } = this.props; 12 | return (gpsLongitude / 10000000).toFixed(5); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/preview/BatteryCurrent.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class BatteryCurrent extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | batteryCurrent: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { batteryCurrent } = this.props; 12 | 13 | return `${batteryCurrent.toFixed(1)}A`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/BatteryVoltage.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class BatteryVoltage extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | batteryVoltage: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { batteryVoltage } = this.props; 12 | 13 | return `${batteryVoltage.toFixed(1)}V`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/BatteryConsumed.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class BatteryConsumed extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | batteryConsumed: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { batteryConsumed } = this.props; 12 | 13 | return `${batteryConsumed.toFixed(0)}mah`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/BatteryRemaining.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class BatteryRemaining extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | batteryCurrent: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { batteryRemaining } = this.props; 12 | 13 | return `${batteryRemaining.toFixed(0)}%`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | 4 | matrix: 5 | include: 6 | - os: linux 7 | sudo: required 8 | dist: trusty 9 | node_js: 10 | - "5.9.0" 11 | 12 | addons: 13 | apt: 14 | packages: 15 | - libnotify4 16 | - icnsutils 17 | - graphicsmagick 18 | 19 | install: 20 | - nvm install 5.9.0 21 | - npm prune 22 | - npm install 23 | 24 | script: 25 | - npm run lint 26 | - npm run test-ci 27 | - npm run validate 28 | -------------------------------------------------------------------------------- /src/preview/HomeLatitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class HomeLatitude extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | homeLatitude: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { gpsLatitude } = this.props; 12 | const lat = (gpsLatitude / 10000000).toFixed(5); 13 | return `H ${lat}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/HomeLongitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class HomeLongitude extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | homeLongitude: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | const { gpsLongitude } = this.props; 12 | const long = (gpsLongitude / 10000000).toFixed(5); 13 | return `H ${long}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/fonts.js: -------------------------------------------------------------------------------- 1 | import small from '../data/fonts/small'; 2 | import medium from '../data/fonts/medium'; 3 | import large from '../data/fonts/large'; 4 | 5 | function getFont(size) { 6 | switch (size) { 7 | case 0: 8 | return small; 9 | case 1: 10 | return medium; 11 | case 2: 12 | return large; 13 | default: 14 | throw new Error(`trying to get unsupported font for size ${size}`); 15 | } 16 | } 17 | 18 | export default { getFont }; 19 | -------------------------------------------------------------------------------- /src/utils/icons.js: -------------------------------------------------------------------------------- 1 | import small from '../data/icons/small'; 2 | import medium from '../data/icons/medium'; 3 | 4 | function getFont(size) { 5 | switch (size) { 6 | case 0: 7 | return small; 8 | case 1: 9 | return medium; 10 | case 2: 11 | // TODO: pixel icons for large font 12 | return medium; 13 | default: 14 | throw new Error('trying to get unsupported icon font for size', size); 15 | } 16 | } 17 | 18 | export default { getFont }; 19 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | csslint: 4 | enabled: true 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - ruby 10 | - javascript 11 | - python 12 | - php 13 | eslint: 14 | enabled: true 15 | fixme: 16 | enabled: true 17 | ratings: 18 | paths: 19 | - "**.css" 20 | - "**.inc" 21 | - "**.js" 22 | - "**.jsx" 23 | - "**.module" 24 | - "**.php" 25 | - "**.py" 26 | - "**.rb" 27 | exclude_paths: 28 | - test/ 29 | -------------------------------------------------------------------------------- /src/preview/actions.js: -------------------------------------------------------------------------------- 1 | export const PANEL = 'preview/panels'; 2 | export const TOGGLE_GRID = 'parameters/toggle_grid'; 3 | export const ALARM = 'parameters/alarm'; 4 | 5 | function setAlarm(alarm) { 6 | return { type: ALARM, payload: alarm }; 7 | } 8 | 9 | function setPanel(panel) { 10 | return { type: PANEL, payload: panel }; 11 | } 12 | 13 | function toggleGrid() { 14 | return { type: TOGGLE_GRID }; 15 | } 16 | 17 | export default { 18 | setAlarm, 19 | setPanel, 20 | toggleGrid, 21 | }; 22 | -------------------------------------------------------------------------------- /src/preview/GpsHdop.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import * as icons from '../data/icons/lookup'; 4 | 5 | export default class GpsHdop extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | gpsHdop: PropTypes.number.isRequired, 9 | } 10 | 11 | icon() { 12 | return icons.HDOP; 13 | } 14 | 15 | content() { 16 | const { gpsHdop } = this.props; 17 | return `${(gpsHdop / 100).toFixed(1)}`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/store/configureStore.production.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { hashHistory } from 'react-router'; 4 | import { routerMiddleware } from 'react-router-redux'; 5 | import rootReducer from '../reducers'; 6 | 7 | const router = routerMiddleware(hashHistory); 8 | 9 | const enhancer = applyMiddleware(thunk, router); 10 | 11 | export default function configureStore(initialState) { 12 | return createStore(rootReducer, initialState, enhancer); 13 | } 14 | -------------------------------------------------------------------------------- /src/preview/AbsoluteAltitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | 5 | export default class AbsoluteAltitude extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | absoluteAltitude: PropTypes.number.isRequired, 9 | units: PropTypes.number.isRequired, 10 | } 11 | 12 | content() { 13 | return `AA ${units.convertDistance(this.props.absoluteAltitude, this.props.units)}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/RelativeAltitude.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | 5 | export default class RelativeAltitude extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | relativeAltitude: PropTypes.number.isRequired, 9 | units: PropTypes.number.isRequired, 10 | } 11 | 12 | content() { 13 | return `A ${units.convertDistance(this.props.relativeAltitude, this.props.units)}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/preview/Watt.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class Watt extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | batteryCurrent: PropTypes.number.isRequired, 8 | batteryVoltage: PropTypes.number.isRequired, 9 | } 10 | 11 | content() { 12 | const { batteryCurrent, batteryVoltage } = this.props; 13 | const watt = batteryVoltage * batteryCurrent; 14 | 15 | return `${watt.toFixed(1)}W`; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/config/parameters/Text.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Label from '../../components/Label'; 3 | import styles from './Text.css'; 4 | 5 | export default function Text(props) { 6 | const { label, text } = props; 7 | return ( 8 |
9 |
12 | ); 13 | } 14 | 15 | Text.propTypes = { 16 | label: PropTypes.string.isRequired, 17 | text: PropTypes.oneOfType([ 18 | PropTypes.string, PropTypes.number, 19 | ]).isRequired, 20 | }; 21 | -------------------------------------------------------------------------------- /src/preview/TotalTrip.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | import * as icons from '../data/icons/lookup'; 5 | 6 | export default class TotalTrip extends StringPreview { 7 | static propTypes = { 8 | ...StringPreview.propTypes, 9 | totalTrip: PropTypes.number.isRequired, 10 | units: PropTypes.number.isRequired, 11 | } 12 | 13 | icon() { 14 | return icons.TOTAL_TRIP; 15 | } 16 | 17 | content() { 18 | return units.convertDistance(this.props.totalTrip, this.props.units); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/preview/WpDistance.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | import * as icons from '../data/icons/lookup'; 5 | 6 | export default class WpDistance extends StringPreview { 7 | static propTypes = { 8 | ...StringPreview.propTypes, 9 | wpDistance: PropTypes.number.isRequired, 10 | units: PropTypes.number.isRequired, 11 | } 12 | 13 | icon() { 14 | return icons.WP_DISTANCE; 15 | } 16 | 17 | content() { 18 | return `${units.convertDistance(this.props.wpDistance, this.props.units)}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/preview/HomeDistance.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | import * as icons from '../data/icons/lookup'; 5 | 6 | export default class HomeDistance extends StringPreview { 7 | static propTypes = { 8 | ...StringPreview.propTypes, 9 | homeDistance: PropTypes.number.isRequired, 10 | units: PropTypes.number.isRequired, 11 | } 12 | 13 | icon() { 14 | return icons.HOME_DISTANCE; 15 | } 16 | 17 | content() { 18 | return `${units.convertDistance(this.props.homeDistance, this.props.units)}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Column.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function Column(props) { 5 | const { children, width } = props; 6 | const style = props.style || {}; 7 | style.width = `${width}%`; 8 | const classes = classNames('column', props.classes); 9 | 10 | return ( 11 |
12 | {children} 13 |
14 | ); 15 | } 16 | 17 | Column.propTypes = { 18 | children: PropTypes.node, 19 | style: PropTypes.object, 20 | classes: PropTypes.string, 21 | width: PropTypes.number.isRequired, 22 | }; 23 | -------------------------------------------------------------------------------- /src/preview/SpeedAir.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | 5 | export default class SpeedAir extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | speedAir: PropTypes.number.isRequired, 9 | units: PropTypes.number.isRequired, 10 | } 11 | 12 | content() { 13 | var speedUnitString = units.speedUnits(this.props.units); 14 | const speed = units.convertSpeedWithoutUnits(this.props.speedAir, this.props.units); 15 | return `AS ${speed.toFixed(0)}${speedUnitString}`; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/preview/SpeedGround.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | 5 | export default class SpeedGround extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | speedGround: PropTypes.number.isRequired, 9 | units: PropTypes.number.isRequired, 10 | } 11 | 12 | content() { 13 | var speedUnitString = units.speedUnits(this.props.units); 14 | const speed = units.convertSpeedWithoutUnits(this.props.speedGround, this.props.units); 15 | return `GS ${speed.toFixed(0)}${speedUnitString}`; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/config/parameters/ParameterList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Card, CardTitle, CardText } from 'react-toolbox/lib/card'; 3 | 4 | export default function ParamterList(props) { 5 | const { children, name } = props; 6 | const contentStyle = { padding: 0 }; 7 | return ( 8 |
9 | 10 | 11 | 12 | {children} 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | ParamterList.propTypes = { 20 | children: PropTypes.node, 21 | name: PropTypes.string.isRequired 22 | }; 23 | -------------------------------------------------------------------------------- /src/preview/reducer.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions'; 2 | import Immutable from 'immutable'; 3 | 4 | const initialState = Immutable.fromJS({ 5 | panel: 0, 6 | alarm: 0, 7 | showGrid: false, 8 | }); 9 | 10 | export default function preview(state = initialState, action = {}) { 11 | const payload = action.payload; 12 | 13 | switch (action.type) { 14 | case actions.ALARM: 15 | return state.set('alarm', payload); 16 | case actions.PANEL: 17 | return state.set('panel', payload); 18 | case actions.TOGGLE_GRID: 19 | return state.update('showGrid', (show) => !show); 20 | default: 21 | return state; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import extensiblePolyfill from 'extensible-polyfill'; 2 | extensiblePolyfill('immutable'); 3 | 4 | import chai from 'chai'; 5 | import chaiImmutable from 'chai-immutable'; 6 | import { jsdom } from 'jsdom'; 7 | 8 | chai.use(chaiImmutable); 9 | 10 | global.document = jsdom(''); 11 | global.window = document.defaultView; 12 | global.navigator = global.window.navigator; 13 | window.localStorage = window.sessionStorage = { 14 | getItem(key) { 15 | return this[key]; 16 | }, 17 | setItem(key, value) { 18 | this[key] = value; 19 | }, 20 | removeItem(key) { 21 | this[key] = undefined; 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/config/parameters/Enabled.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Checkbox from 'react-toolbox/lib/checkbox/Checkbox'; 3 | 4 | export default class ParameterEnabled extends Component { 5 | static propTypes = { 6 | enabled: PropTypes.bool.isRequired, 7 | setEnabled: PropTypes.func.isRequired 8 | } 9 | 10 | _onChange = () => { 11 | this.props.setEnabled(!this.props.enabled); 12 | } 13 | 14 | render() { 15 | const { enabled } = this.props; 16 | 17 | return ( 18 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/preview/Alarms.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class Alarms extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | alarm: PropTypes.number.isRequired, 8 | } 9 | 10 | content() { 11 | switch (this.props.alarm) { 12 | case 1: 13 | return 'NO GPS FIX'; 14 | case 2: 15 | return 'LOW BATTERY'; 16 | case 3: 17 | return 'SPEED LOW'; 18 | case 4: 19 | return 'OVER SPEED'; 20 | case 5: 21 | return 'LOW ALT'; 22 | case 6: 23 | return 'HIGH ALT'; 24 | default: 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | const express = require('express'); 5 | const webpack = require('webpack'); 6 | const config = require('./webpack.config.development'); 7 | 8 | const app = express(); 9 | const compiler = webpack(config); 10 | 11 | const PORT = 3000; 12 | 13 | app.use(require('webpack-dev-middleware')(compiler, { 14 | publicPath: config.output.publicPath, 15 | stats: { 16 | colors: true 17 | } 18 | })); 19 | 20 | app.use(require('webpack-hot-middleware')(compiler)); 21 | 22 | app.listen(PORT, 'localhost', err => { 23 | if (err) { 24 | console.log(err); 25 | return; 26 | } 27 | 28 | console.log(`Listening at http://localhost:${PORT}`); 29 | }); 30 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var: 0, func-names: 0, strict: 0, object-shorthand: 0 */ 2 | 'use strict'; 3 | 4 | var webpackConfig = require('./webpack.config.development'); 5 | var wallabyWebpack = require('wallaby-webpack'); 6 | var webpackPostprocessor = wallabyWebpack(webpackConfig); 7 | 8 | module.exports = function (wallaby) { 9 | return { 10 | files: [ 11 | { pattern: 'app/**/*.js', load: false } 12 | ], 13 | 14 | tests: [ 15 | { pattern: 'test/**/*.js', load: false } 16 | ], 17 | 18 | compilers: { 19 | '**/*.js': wallaby.compilers.babel() 20 | }, 21 | 22 | postprocessor: webpackPostprocessor, 23 | 24 | bootstrap: function () { 25 | window.__moduleBundler.loadTests(); 26 | } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/preview/Grid.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import styles from './Grid.css'; 4 | 5 | export default function Grid(props) { 6 | if (!props.visible) { 7 | return null; 8 | } 9 | 10 | return ( 11 |
12 |
13 | {[...Array(26)].map((_, i) => 14 |
)} 15 |
16 |
17 | {[...Array(35)].map((_, i) => 18 |
)} 19 |
20 |
21 | ); 22 | } 23 | 24 | Grid.propTypes = { 25 | visible: PropTypes.bool.isRequired, 26 | }; 27 | -------------------------------------------------------------------------------- /src/preview/Efficiency.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import units from '../utils/units'; 4 | 5 | export default class Efficiency extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | batteryCurrent: PropTypes.number.isRequired, 9 | batteryVoltage: PropTypes.number.isRequired, 10 | speedGround: PropTypes.number.isRequired, 11 | units: PropTypes.number.isRequired, 12 | } 13 | 14 | content() { 15 | const { batteryCurrent, batteryVoltage, speedGround } = this.props; 16 | const wattage = batteryVoltage * batteryCurrent; 17 | const efficiency = wattage / speedGround; 18 | const distanceString = units.longDistanceUnits(this.props.units); 19 | 20 | return `${efficiency.toFixed(1)}W/${distanceString}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/config/parameters/Scale.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Input from '../../components/Input'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | 5 | export default class Scale extends Component { 6 | static propTypes = { 7 | name: PropTypes.string.isRequired, 8 | scale: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setScale: PropTypes.func.isRequired, 10 | } 11 | 12 | _onChange = (scale) => { 13 | this.props.setScale(this.props.name, parseFloat(scale)); 14 | } 15 | 16 | render() { 17 | const { scale } = this.props; 18 | return ( 19 |
20 | 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.changelogrc: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "Playuav OSD Configurator", 3 | "tag": false, 4 | "sections": [ 5 | { 6 | "title": "Bug Fixes", 7 | "grep": "^fix" 8 | }, 9 | { 10 | "title": "Features", 11 | "grep": "^feat" 12 | }, 13 | { 14 | "title": "Documentation", 15 | "grep": "^docs" 16 | }, 17 | { 18 | "title": "Breaking changes", 19 | "grep": "BREAKING" 20 | }, 21 | { 22 | "title": "Refactor", 23 | "grep": "^refactor" 24 | }, 25 | { 26 | "title": "Style", 27 | "grep": "^style" 28 | }, 29 | { 30 | "title": "Test", 31 | "grep": "^test" 32 | }, 33 | { 34 | "title": "Chore", 35 | "grep": "^chore" 36 | }, 37 | { 38 | "title": "Branchs merged", 39 | "grep": "^Merge branch" 40 | }, 41 | { 42 | "title" : "Pull requests merged", 43 | "grep": "^Merge pull request" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/config/parameters/SpeedScaleType.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | 5 | export default class SpeedScaleType extends Component { 6 | static propTypes = { 7 | name: PropTypes.string.isRequired, 8 | scaleType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setScaleType: PropTypes.func.isRequired, 10 | } 11 | 12 | _onChange = (scaleType) => { 13 | this.props.setScaleType(this.props.name, scaleType); 14 | } 15 | 16 | render() { 17 | const { scaleType } = this.props; 18 | const options = [ 19 | { value: 0, label: 'ground' }, 20 | { value: 1, label: 'air' }, 21 | ]; 22 | 23 | return ( 24 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/xcuserdata/sergiovilar.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | blablabla.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | scheme.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 0396ABFD1B605094004125D8 21 | 22 | primary 23 | 24 | 25 | 0396AC131B605094004125D8 26 | 27 | primary 28 | 29 | 30 | 0396AC1E1B605094004125D8 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/config/parameters/index.js: -------------------------------------------------------------------------------- 1 | import AltitudeScaleType from './AltitudeScaleType'; 2 | import Enabled from './Enabled'; 3 | import FontSize from './FontSize'; 4 | import HorizontalAlignment from './HorizontalAlignment'; 5 | import ParameterList from './ParameterList'; 6 | import Position from './Position'; 7 | import Radius from './Radius'; 8 | import Scale from './Scale'; 9 | import ScaleAlignment from './ScaleAlignment'; 10 | import Select from './Select'; 11 | import SpeedScaleType from './SpeedScaleType'; 12 | import Text from './Text'; 13 | import Units from './Units'; 14 | import VerticalAlignment from './VerticalAlignment'; 15 | import VideoMode from './VideoMode'; 16 | import VisibleOn from './VisibleOn'; 17 | 18 | export default { 19 | AltitudeScaleType, 20 | Enabled, 21 | FontSize, 22 | HorizontalAlignment, 23 | ParameterList, 24 | Position, 25 | Radius, 26 | Scale, 27 | ScaleAlignment, 28 | Select, 29 | SpeedScaleType, 30 | Text, 31 | Units, 32 | VerticalAlignment, 33 | VideoMode, 34 | VisibleOn, 35 | }; 36 | -------------------------------------------------------------------------------- /src/preview/LinkQuality.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import * as icons from '../data/icons/lookup'; 4 | 5 | export default class LinkQuality extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | max: PropTypes.number.isRequired, 9 | min: PropTypes.number.isRequired, 10 | raw: PropTypes.number.isRequired, 11 | linkQuality: PropTypes.number.isRequired, 12 | } 13 | 14 | icon() { 15 | return icons.LINK_QUALITY; 16 | } 17 | 18 | content() { 19 | const { min, max, raw } = this.props; 20 | let { linkQuality } = this.props; 21 | let content = ''; 22 | 23 | if (raw === 0) { 24 | if ((max - min) > 0) { 25 | linkQuality = (linkQuality - min) / (max - min) * 100; 26 | } 27 | 28 | linkQuality = Math.max(0, linkQuality); 29 | content = `${linkQuality.toFixed(0)}%`; 30 | } else { 31 | content = `${linkQuality.toFixed(0)}`; 32 | } 33 | return content; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "minimum_chrome_version": "38", 4 | "version": "0.12.1", 5 | "author": "TobiasBales", 6 | "name": "PlayUAV OSD Configurator", 7 | "short_name": "playuavosdconfigurator", 8 | "description": "Crossplatform configuration tool for PlayUAV OSD", 9 | "offline_enabled": true, 10 | "app": { 11 | "background": { 12 | "scripts": ["./dist/eventPage.js"], 13 | "persistent": false 14 | } 15 | }, 16 | "permissions": [ 17 | "https://www.google-analytics.com/", 18 | "https://*.github.com/", 19 | "https://*.githubusercontent.com/", 20 | "serial", 21 | "usb", 22 | "storage", 23 | "fileSystem", 24 | "fileSystem.write", 25 | "fileSystem.retainEntries", 26 | "notifications" 27 | ], 28 | "optional_permissions": [ 29 | {"usbDevices": [ 30 | {"vendorId": 9900, "productId": 2} 31 | ]} 32 | ], 33 | "icons": { 34 | "128": "icon_128.png" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import BaseInput from 'react-toolbox/lib/input'; 3 | import CustomPropTypes from '../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class Input extends Component { 7 | static propTypes = { 8 | label: PropTypes.string, 9 | onChange: PropTypes.func.isRequired, 10 | step: PropTypes.number, 11 | type: PropTypes.string, 12 | value: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | } 14 | 15 | static defaultProps = { 16 | type: 'string', 17 | } 18 | 19 | render() { 20 | const value = this.props.value; 21 | const classes = classNames( 22 | { modified: value.get('value') !== value.get('originalValue'), }, 23 | ); 24 | 25 | return ( 26 | 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/config/parameters/Radius.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Input from '../../components/Input'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | 5 | export default class Radius extends Component { 6 | static propTypes = { 7 | label: PropTypes.string.isRequired, 8 | name: PropTypes.string.isRequired, 9 | radius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 10 | radiusKey: PropTypes.string.isRequired, 11 | setRadius: PropTypes.func.isRequired, 12 | step: PropTypes.number, 13 | } 14 | 15 | static defaultProps = { 16 | step: 0.1, 17 | } 18 | 19 | _onChange(radius) { 20 | this.props.setRadius(this.props.name, this.props.radiusKey, parseInt(radius, 10)); 21 | } 22 | 23 | render() { 24 | const { radius, label } = this.props; 25 | 26 | return ( 27 |
28 | 35 |
36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/preview/Rssi.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import * as icons from '../data/icons/lookup'; 4 | 5 | export default class Rssi extends StringPreview { 6 | static propTypes = { 7 | ...StringPreview.propTypes, 8 | max: PropTypes.number.isRequired, 9 | min: PropTypes.number.isRequired, 10 | raw: PropTypes.number.isRequired, 11 | rssi: PropTypes.number.isRequired, 12 | type: PropTypes.number.isRequired, 13 | } 14 | 15 | icon() { 16 | return icons.RSSI; 17 | } 18 | 19 | content() { 20 | const { raw, type } = this.props; 21 | let { max, min, rssi } = this.props; 22 | let content = ''; 23 | 24 | if (raw === 0) { 25 | if (type === 0) { 26 | min = Math.max(0, min); 27 | max = Math.min(255, max); 28 | } 29 | 30 | if ((max - min) > 0) { 31 | rssi = (rssi - min) / (max - min) * 100; 32 | } 33 | 34 | rssi = Math.max(0, rssi); 35 | content = `${rssi.toFixed(0)}%`; 36 | } else { 37 | content = `${rssi.toFixed(0)}`; 38 | } 39 | return content; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/config/parameters/VideoMode.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class VideoMode extends Component { 7 | static propTypes = { 8 | videoMode: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setMode: PropTypes.func.isRequired, 10 | } 11 | 12 | _onChange = (videoMode) => { 13 | this.props.setMode('video', 'video', videoMode); 14 | } 15 | 16 | render() { 17 | const { videoMode } = this.props; 18 | const options = [ 19 | { value: 0, label: 'ntsc' }, 20 | { value: 1, label: 'pal' }, 21 | ]; 22 | const classes = classNames({ 23 | modified: videoMode.get('value') !== videoMode.get('originalValue') 24 | }); 25 | 26 | return ( 27 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 C. T. Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/config/parameters/Units.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class Units extends Component { 7 | static propTypes = { 8 | units: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setUnits: PropTypes.func.isRequired, 10 | name: PropTypes.string.isRequired, 11 | } 12 | 13 | _onChange = (units) => { 14 | this.props.setUnits(this.props.name, units); 15 | } 16 | 17 | render() { 18 | const { units } = this.props; 19 | const options = [ 20 | { value: 0, label: 'metric' }, 21 | { value: 1, label: 'imperial' }, 22 | ]; 23 | const classes = classNames({ 24 | modified: units.get('value') !== units.get('originalValue') 25 | }); 26 | 27 | return ( 28 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/config/parameters/AltitudeScaleType.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class AltitudeScaleType extends Component { 7 | static propTypes = { 8 | scaleType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setScaleType: PropTypes.func.isRequired, 10 | } 11 | 12 | _onChange = (scaleType) => { 13 | this.props.setScaleType('altitudeScale', scaleType); 14 | } 15 | 16 | render() { 17 | const value = this.props.scaleType; 18 | const options = [ 19 | { value: 0, label: 'absolute' }, 20 | { value: 1, label: 'relative' }, 21 | ]; 22 | const classes = classNames( 23 | { modified: value.get('value') !== value.get('originalValue') } 24 | ); 25 | 26 | return ( 27 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pixler/Preview.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import fonts from '../utils/fonts'; 3 | import Canvas from '../utils/Canvas'; 4 | import styles from './Preview.css'; 5 | 6 | export default class Preview extends Component { 7 | static propTypes = { 8 | fontSize: PropTypes.number.isRequired, 9 | outline: PropTypes.arrayOf(PropTypes.number).isRequired, 10 | shape: PropTypes.arrayOf(PropTypes.number).isRequired, 11 | } 12 | 13 | componentDidMount() { 14 | this.canvas = new Canvas(this.refs.canvas); 15 | this.draw(); 16 | } 17 | 18 | componentDidUpdate() { 19 | this.draw(); 20 | } 21 | 22 | draw() { 23 | const font = fonts.getFont(this.props.fontSize); 24 | const { height, width } = font.dimensions; 25 | const { shape, outline } = this.props; 26 | this.canvas.clear(); 27 | this.canvas.drawCharacterData(height / 2, width / 2, shape, outline, width, height); 28 | } 29 | 30 | render() { 31 | const { height, width } = fonts.getFont(this.props.fontSize).dimensions; 32 | return ( 33 | 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/preview/GpsStatus.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import * as icons from '../data/icons/lookup'; 4 | 5 | const NO_GPS = 0; 6 | const NO_FIX = 1; 7 | const FIX_2D = 2; 8 | const FIX_3D = 3; 9 | const FIX_3D_DGPS = 4; 10 | 11 | export default class GpsStatus extends StringPreview { 12 | static propTypes = { 13 | ...StringPreview.propTypes, 14 | gpsSattelites: PropTypes.number.isRequired, 15 | gpsStatus: PropTypes.number.isRequired, 16 | } 17 | 18 | icon() { 19 | return icons.GPS; 20 | } 21 | 22 | content() { 23 | const { gpsSattelites, gpsStatus } = this.props; 24 | let content = ''; 25 | 26 | switch (gpsStatus) { 27 | case NO_GPS: 28 | content = 'NOFIX'; 29 | break; 30 | case NO_FIX: 31 | content = 'NOFIX'; 32 | break; 33 | case FIX_2D: 34 | content = `${gpsSattelites}`; 35 | break; 36 | case FIX_3D: 37 | content = `${gpsSattelites}`; 38 | break; 39 | case FIX_3D_DGPS: 40 | content = `${gpsSattelites}`; 41 | break; 42 | default: 43 | content = 'unknown'; 44 | } 45 | return content; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pixler/Pixel.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styles from './Pixel.css'; 3 | import classNames from 'classnames'; 4 | import { EMPTY, OUTLINE, SHAPE } from './actions'; 5 | 6 | export default class Pixel extends Component { 7 | static propTypes = { 8 | setPixel: PropTypes.func.isRequired, 9 | type: PropTypes.oneOf([EMPTY, SHAPE, OUTLINE]).isRequired, 10 | } 11 | 12 | _onClick = (e) => { 13 | let targetType = EMPTY; 14 | 15 | if (e.button === 0) { 16 | targetType = SHAPE; 17 | } else if (e.button === 2) { 18 | targetType = OUTLINE; 19 | } 20 | 21 | if (targetType === this.props.type) { 22 | targetType = EMPTY; 23 | } 24 | this.props.setPixel(targetType); 25 | }; 26 | 27 | render() { 28 | const classDescriptions = {}; 29 | classDescriptions[styles.empty] = this.props.type === EMPTY; 30 | classDescriptions[styles.outline] = this.props.type === OUTLINE; 31 | classDescriptions[styles.shape] = this.props.type === SHAPE; 32 | const classes = classNames(styles.pixel, classDescriptions); 33 | return ( 34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/config/parameters/ScaleAlignment.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class ScaleAlignment extends Component { 7 | static propTypes = { 8 | name: PropTypes.string.isRequired, 9 | scaleAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 10 | setScaleAlignment: PropTypes.func.isRequired, 11 | } 12 | 13 | _onChange = (scaleAlignment) => { 14 | this.props.setScaleAlignment(this.props.name, scaleAlignment); 15 | } 16 | 17 | render() { 18 | const value = this.props.scaleAlignment; 19 | const options = [ 20 | { value: 0, label: 'left' }, 21 | { value: 1, label: 'right' }, 22 | ]; 23 | const classes = classNames( 24 | { modified: value.get('value') !== value.get('originalValue') } 25 | ); 26 | 27 | return ( 28 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/preview/Compass.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import PreviewBase from './PreviewBase'; 3 | 4 | export default class Compass extends PreviewBase { 5 | static propTypes = { 6 | heading: PropTypes.number.isRequired, 7 | panel: PropTypes.number.isRequired, 8 | positionY: PropTypes.number.isRequired, 9 | visibleOn: PropTypes.number.isRequired, 10 | } 11 | 12 | draw() { 13 | this.canvas.clear(); 14 | const posY = 8; 15 | 16 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 17 | this.canvas.drawCompass(this.props.heading, posY); 18 | } 19 | } 20 | 21 | render() { 22 | const { positionY } = this.props; 23 | const positionX = 180; // should be half width 24 | const width = 180; 25 | const height = 40; 26 | 27 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 28 | 29 | return ( 30 | !visible ? 31 | : 32 | 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/config/parameters/FontSize.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class Position extends Component { 7 | static propTypes = { 8 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setFontSize: PropTypes.func.isRequired, 10 | name: PropTypes.string.isRequired, 11 | } 12 | 13 | _onChange = (fontSize) => { 14 | this.props.setFontSize(this.props.name, fontSize); 15 | } 16 | 17 | render() { 18 | const { fontSize } = this.props; 19 | const options = [ 20 | { value: 0, label: 'small' }, 21 | { value: 1, label: 'medium' }, 22 | { value: 2, label: 'large' }, 23 | ]; 24 | 25 | const classes = classNames( 26 | { modified: fontSize.get('value') !== fontSize.get('originalValue') }, 27 | ); 28 | 29 | return ( 30 | 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/config/parameters/Select.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class Select extends Component { 7 | static propTypes = { 8 | label: PropTypes.string.isRequired, 9 | setValue: PropTypes.func.isRequired, 10 | options: React.PropTypes.arrayOf( 11 | React.PropTypes.shape({ 12 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, 13 | label: PropTypes.string.isRequired, 14 | })).isRequired, 15 | value: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | } 17 | 18 | _onChange = (value) => { 19 | this.props.setValue(value); 20 | } 21 | 22 | render() { 23 | const { value, options, label } = this.props; 24 | const classes = classNames( 25 | { modified: value.get('value') !== value.get('originalValue') } 26 | ); 27 | 28 | return ( 29 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config/parameters/HorizontalAlignment.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class HorizontalAlignment extends Component { 7 | static propTypes = { 8 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | setHAlignment: PropTypes.func.isRequired, 10 | name: PropTypes.string.isRequired, 11 | } 12 | 13 | _onChange = (hAlignment) => { 14 | this.props.setHAlignment(this.props.name, hAlignment); 15 | } 16 | 17 | render() { 18 | const { hAlignment } = this.props; 19 | const options = [ 20 | { value: 0, label: 'left' }, 21 | { value: 1, label: 'middle' }, 22 | { value: 2, label: 'right' }, 23 | ]; 24 | const classes = classNames( 25 | { modified: hAlignment.get('value') !== hAlignment.get('originalValue') } 26 | ); 27 | 28 | return ( 29 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config/parameters/VerticalAlignment.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Dropdown from 'react-toolbox/lib/dropdown/Dropdown'; 3 | import CustomPropTypes from '../../utils/PropTypes'; 4 | import classNames from 'classnames'; 5 | 6 | export default class VerticalAlignment extends Component { 7 | static propTypes = { 8 | name: PropTypes.string.isRequired, 9 | setVAlignment: PropTypes.func.isRequired, 10 | vAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | } 12 | 13 | _onChange = (vAlignment) => { 14 | this.props.setVAlignment(this.props.name, parseInt(vAlignment, 10)); 15 | } 16 | 17 | render() { 18 | const { vAlignment } = this.props; 19 | const options = [ 20 | { value: 0, label: 'top' }, 21 | { value: 1, label: 'middle' }, 22 | { value: 2, label: 'bottom' }, 23 | ]; 24 | const classes = classNames({ 25 | modified: vAlignment.get('value') !== vAlignment.get('originalValue') 26 | }); 27 | 28 | return ( 29 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import extensiblePolyfill from 'extensible-polyfill'; 2 | extensiblePolyfill('immutable'); 3 | 4 | import React from 'react'; 5 | import { render } from 'react-dom'; 6 | import { Provider } from 'react-redux'; 7 | import { Router, hashHistory } from 'react-router'; 8 | import { syncHistoryWithStore } from 'react-router-redux'; 9 | import routes from './routes'; 10 | import configureStore from './store/configureStore'; 11 | import 'material-design-icons-iconfont/dist/material-design-icons.css'; 12 | import './app.global.css'; 13 | import 'react-toolbox/lib/commons.scss'; 14 | import 'roboto-fontface/css/roboto-fontface'; 15 | import injectTapEventPlugin from 'react-tap-event-plugin'; 16 | 17 | const store = configureStore(); 18 | const history = syncHistoryWithStore(hashHistory, store); 19 | 20 | injectTapEventPlugin(); 21 | 22 | render( 23 | 24 | 25 | , 26 | document.getElementById('root') 27 | ); 28 | 29 | if (process.env.NODE_ENV !== 'production') { 30 | // Use require because imports can't be conditional. 31 | // In production, you should ensure process.env.NODE_ENV 32 | // is envified so that Uglify can eliminate this 33 | // module and its dependencies as dead code. 34 | // require('./createDevToolsWindow')(store); 35 | } 36 | -------------------------------------------------------------------------------- /src/pixler/Pixler.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { setPixel } from './actions'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import Editor from './Editor'; 6 | import Preview from './Preview'; 7 | import Column from '../components/Column'; 8 | import Output from './Output'; 9 | 10 | function Pixler(props) { 11 | const { fontSize, outline, shape } = props; 12 | return ( 13 |
14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | Pixler.propTypes = { 31 | setPixel: PropTypes.func.isRequired, 32 | outline: PropTypes.arrayOf(PropTypes.number).isRequired, 33 | fontSize: PropTypes.number.isRequired, 34 | shape: PropTypes.arrayOf(PropTypes.number).isRequired, 35 | }; 36 | 37 | function mapStateToProps(state) { 38 | return state.pixler.toJS(); 39 | } 40 | 41 | function mapDispatchersToProps(dispatch) { 42 | return bindActionCreators({ setPixel }, dispatch); 43 | } 44 | 45 | export default connect(mapStateToProps, mapDispatchersToProps)(Pixler); 46 | -------------------------------------------------------------------------------- /src/config/settings/Startup.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Startup extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | versionSplashMilliseconds: CustomPropTypes.value(PropTypes.number.isRequired).isRequired 11 | }).isRequired, 12 | setVersionSplashMilliseconds: PropTypes.func.isRequired 13 | } 14 | 15 | shouldComponentUpdate(nextProps) { 16 | return !this.props.parameters.equals(nextProps.parameters); 17 | } 18 | 19 | _setValue(key, value) { 20 | this.props.setValue('switching', key, parseInt(value, 10)); 21 | } 22 | 23 | render() { 24 | const { versionSplashMilliseconds } = this.props.parameters; 25 | const { setVersionSplashMilliseconds } = this.props; 26 | 27 | return ( 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/preview/PreviewBase.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | 4 | export default class PreviewBase extends Component { 5 | static propTypes = { 6 | positionX: PropTypes.number.isRequired, 7 | positionY: PropTypes.number.isRequired, 8 | setPosition: PropTypes.func.isRequired, 9 | } 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | left: this.props.positionX, 15 | top: this.props.positionY, 16 | }; 17 | } 18 | 19 | componentDidMount() { 20 | this.canvas = new Canvas(this.refs.canvas); 21 | this.draw(); 22 | } 23 | 24 | shouldComponentUpdate(nextProps) { 25 | return Object.keys(this.props).reduce((shouldUpdate, key) => { 26 | if (key.startsWith('set')) { 27 | return shouldUpdate; 28 | } 29 | return shouldUpdate || this.props[key] !== nextProps[key]; 30 | }, false); 31 | } 32 | 33 | componentDidUpdate() { 34 | this.draw(); 35 | } 36 | 37 | draw() { 38 | } 39 | 40 | _onDragStop = () => { 41 | const { left, top } = this.state; 42 | this.props.setPosition(left, top); 43 | // this.setState({ ...this.state, left: 0, top: 0 }); 44 | } 45 | 46 | _onDrag = (e, ui) => { 47 | const { left, top } = this.state; 48 | this.setState({ 49 | ...this.state, 50 | left: left + ui.deltaX, 51 | top: top + ui.deltaY 52 | }); 53 | } 54 | 55 | render() { 56 | return ; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/preview/Time.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | import * as icons from '../data/icons/lookup'; 4 | 5 | function padNumber(number, digits) { 6 | return (`0000000000000${number}`).slice(-digits); 7 | } 8 | 9 | export default class Time extends StringPreview { 10 | static propTypes = { 11 | ...StringPreview.propTypes, 12 | timeSinceArming: PropTypes.number.isRequired, 13 | timeSinceHeartbeat: PropTypes.number.isRequired, 14 | timeSinceStartup: PropTypes.number.isRequired, 15 | type: PropTypes.number.isRequired, 16 | } 17 | 18 | icon() { 19 | return icons.TIME; 20 | } 21 | 22 | content() { 23 | const { timeSinceArming, timeSinceHeartbeat, timeSinceStartup, type } = this.props; 24 | let time = 0; 25 | switch (type) { 26 | case 0: 27 | time = timeSinceStartup; 28 | break; 29 | case 1: 30 | time = timeSinceHeartbeat; 31 | break; 32 | case 2: 33 | time = timeSinceArming; 34 | break; 35 | default: 36 | break; 37 | } 38 | let content = ''; 39 | 40 | const hours = Math.floor(time / 3600000); 41 | const minutes = Math.floor(time / 60000); 42 | const seconds = Math.floor(time / 1000); 43 | 44 | if (hours > 0) { 45 | content = `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`; 46 | } else { 47 | content = `${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`; 48 | } 49 | 50 | return content; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/preview/VarioGraph.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import PreviewBase from './PreviewBase'; 3 | 4 | export default class VarioGraphPreview extends PreviewBase { 5 | static propTypes = { 6 | panel: PropTypes.number.isRequired, 7 | positionX: PropTypes.number.isRequired, 8 | positionY: PropTypes.number.isRequired, 9 | varioData: PropTypes.arrayOf(PropTypes.number).isRequired, 10 | visibleOn: PropTypes.number.isRequired, 11 | } 12 | 13 | draw() { 14 | this.canvas.clear(); 15 | 16 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 17 | const { height, width } = this.refs.canvas; 18 | const { varioData } = this.props; 19 | this.canvas.drawRectangle(2, 2, width - 4, height - 4, true, true); 20 | const points = []; 21 | 22 | for (let i = 0; i < varioData.length - 1; i++) { 23 | const x = i; 24 | const y = height - (varioData[x] + height / 2); 25 | points.push(x + 3, y + 2); 26 | } 27 | this.canvas.drawSegmentedLine(true, false, points); 28 | } 29 | } 30 | 31 | render() { 32 | const { positionX, positionY } = this.props; 33 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 34 | 35 | return ( 36 | !visible ? 37 | : 38 | 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/store/configureStore.development.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { persistState } from 'redux-devtools'; 3 | import thunk from 'redux-thunk'; 4 | import createLogger from 'redux-logger'; 5 | import { hashHistory } from 'react-router'; 6 | import { routerMiddleware } from 'react-router-redux'; 7 | import rootReducer from '../reducers'; 8 | import DevTools from '../DevTools'; 9 | 10 | const logger = createLogger({ 11 | actionTransformer: (action) => ( 12 | Object.assign({}, action, { 13 | type: String(action.type), 14 | }) 15 | ), 16 | stateTransformer: (state) => { 17 | const plainState = {}; 18 | Object.keys(state).forEach((key) => { 19 | if (typeof state[key].toJS === 'function') { 20 | plainState[key] = state[key].toJS(); 21 | } else { 22 | plainState[key] = state[key]; 23 | } 24 | }); 25 | return plainState; 26 | }, 27 | level: 'info', 28 | collapsed: true, 29 | }); 30 | 31 | const router = routerMiddleware(hashHistory); 32 | 33 | const enhancer = compose( 34 | applyMiddleware(thunk, router, logger), 35 | DevTools.instrument(), 36 | persistState( 37 | window.location.href.match( 38 | /[?&]debug_session=([^&]+)\b/ 39 | ) 40 | ) 41 | ); 42 | 43 | export default function configureStore(initialState) { 44 | const store = createStore(rootReducer, initialState, enhancer); 45 | 46 | if (module.hot) { 47 | module.hot.accept('../reducers', () => 48 | store.replaceReducer(require('../reducers')) 49 | ); 50 | } 51 | 52 | return store; 53 | } 54 | -------------------------------------------------------------------------------- /src/data/icons/small.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | 3 | const shape = [ 4 | [0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x38, 0x7c, 0xfe], // GPS 5 | [0x0, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x0, 0x0], // HDOP 6 | [0x0, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x0, 0x0], // TIME 7 | [0xf8, 0xf8, 0xfe, 0xff, 0xff, 0xef, 0xe0, 0xe0, 0xe0, 0xe0], // WP_DISTANCE 8 | [0x7c, 0x1fe, 0xfc, 0x70, 0x38, 0x38, 0x70, 0xfc, 0x1fe, 0x7c], // TOTAL_TRIP 9 | [0xe0, 0xe0, 0xf8, 0xf8, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe], // RSSI 10 | [0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x38, 0x38, 0x38], // LINK_QUALITY 11 | [0x0, 0x24, 0x7e, 0xff, 0xff, 0xff, 0x7e, 0x24, 0x0, 0x0], // HOME_DISTANCE 12 | ]; 13 | 14 | const outline = [ 15 | [0xc7, 0xbb, 0x7d, 0x6d, 0x7d, 0xbb, 0xc7, 0xd7, 0xbb, 0x1], // GPS 16 | [0x0, 0xc7, 0xab, 0x6d, 0x1, 0x6d, 0xab, 0xc7, 0x0, 0x0], // HDOP 17 | [0x0, 0xc7, 0xab, 0x6d, 0x61, 0x7d, 0xbb, 0xc7, 0x0, 0x0], // TIME 18 | [0x7, 0x57, 0x51, 0x54, 0x45, 0x50, 0x5f, 0x5f, 0x5f, 0x1f], // WP_DISTANCE 19 | [0x83, 0x7d, 0x43, 0xaf, 0xd7, 0xd7, 0xaf, 0x43, 0x7d, 0x83], // TOTAL_TRIP 20 | [0x1f, 0x5f, 0x47, 0x57, 0x51, 0x55, 0x55, 0x55, 0x55, 0x0], // RSSI 21 | [0xef, 0xd7, 0x93, 0x55, 0xbb, 0xd7, 0xd7, 0xd7, 0xd7, 0xc7], // LINK_QUALITY 22 | [0x80, 0xdb, 0xa5, 0x42, 0x7e, 0x42, 0xa5, 0xdb, 0xff, 0xff], // HOME_DISTANCE 23 | ]; 24 | 25 | const dimensions = { width: 8, height: 14 }; 26 | 27 | function getData(index) { 28 | return { shape: shape[index], outline: outline[index].map((b) => (~b >>> 0) & Math.pow(2, dimensions.width) - 1) }; 29 | } 30 | 31 | export default { dimensions, getData }; 32 | -------------------------------------------------------------------------------- /src/config/settings/VarioGraph.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | 6 | export default class VarioGraph extends Component { 7 | static propTypes = { 8 | parameters: ImmutablePropTypes.contains({ 9 | numberOfPanels: PropTypes.number.isRequired, 10 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | }).isRequired, 14 | setPosition: PropTypes.func.isRequired, 15 | setVisibleOn: PropTypes.func.isRequired, 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | return !this.props.parameters.equals(nextProps.parameters); 20 | } 21 | 22 | render() { 23 | const { setPosition, setVisibleOn } = this.props; 24 | const { numberOfPanels, positionX, positionY, visibleOn } = this.props.parameters; 25 | 26 | return ( 27 | 28 | 31 | 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/config/settings/Wind.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | 6 | export default class Wind extends Component { 7 | static propTypes = { 8 | parameters: ImmutablePropTypes.contains({ 9 | numberOfPanels: PropTypes.number.isRequired, 10 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | }).isRequired, 14 | setPosition: PropTypes.func.isRequired, 15 | setVisibleOn: PropTypes.func.isRequired, 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | return !this.props.parameters.equals(nextProps.parameters); 20 | } 21 | 22 | render() { 23 | const { 24 | setPosition, 25 | setVisibleOn, 26 | } = this.props; 27 | const { 28 | numberOfPanels, 29 | positionX, 30 | positionY, 31 | visibleOn, 32 | } = this.props.parameters; 33 | 34 | return ( 35 | 36 | 39 | 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config/settings/RCChannels.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class RCChannels extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | numberOfPanels: PropTypes.number.isRequired, 11 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | }).isRequired, 15 | setPosition: PropTypes.func.isRequired, 16 | setVisibleOn: PropTypes.func.isRequired, 17 | } 18 | 19 | shouldComponentUpdate(nextProps) { 20 | return !this.props.parameters.equals(nextProps.parameters); 21 | } 22 | 23 | render() { 24 | const { setPosition, setVisibleOn } = this.props; 25 | const { 26 | numberOfPanels, positionX, positionY, visibleOn 27 | } = this.props.parameters; 28 | return ( 29 | 30 | 33 | 36 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/preview/ArtificialHorizon.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import PreviewBase from './PreviewBase'; 3 | 4 | export default class AttitudeMp extends PreviewBase { 5 | static propTypes = { 6 | panel: PropTypes.number.isRequired, 7 | pitch: PropTypes.number.isRequired, 8 | positionX: PropTypes.number.isRequired, 9 | positionY: PropTypes.number.isRequired, 10 | roll: PropTypes.number.isRequired, 11 | scale: PropTypes.number.isRequired, 12 | type: PropTypes.number.isRequired, 13 | visibleOn: PropTypes.number.isRequired, 14 | yaw: PropTypes.number.isRequired, 15 | } 16 | 17 | draw() { 18 | this.canvas.clear(); 19 | const width = this.refs.canvas.width; 20 | const height = this.refs.canvas.height; 21 | 22 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 23 | if (this.props.type === 0) { 24 | this.canvas.drawAttitudeMp(width, height, 25 | this.props.roll, this.props.pitch, this.props.scale); 26 | } else { 27 | this.canvas.drawAttitudeSimple(width, height, 28 | this.props.roll, this.props.pitch, this.props.scale); 29 | } 30 | } 31 | } 32 | 33 | render() { 34 | const { positionX, positionY } = this.props; 35 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 36 | 37 | return ( 38 | !visible ? 39 | : 40 | 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/config/settings/HomeDirection.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | 6 | export default class HomeDirection extends Component { 7 | static propTypes = { 8 | parameters: ImmutablePropTypes.contains({ 9 | numberOfPanels: PropTypes.number.isRequired, 10 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | }).isRequired, 14 | setPosition: PropTypes.func.isRequired, 15 | setVisibleOn: PropTypes.func.isRequired, 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | return !this.props.parameters.equals(nextProps.parameters); 20 | } 21 | 22 | render() { 23 | const { 24 | setPosition, 25 | setVisibleOn, 26 | } = this.props; 27 | const { 28 | numberOfPanels, 29 | positionX, 30 | positionY, 31 | visibleOn, 32 | } = this.props.parameters; 33 | 34 | return ( 35 | 36 | 39 | 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config/settings/Compass.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Input from '../../components/Input'; 4 | import Parameters from '../parameters'; 5 | import Column from '../../components/Column'; 6 | import CustomPropTypes from '../../utils/PropTypes'; 7 | 8 | export default class Compass extends Component { 9 | static propTypes = { 10 | parameters: ImmutablePropTypes.contains({ 11 | numberOfPanels: PropTypes.number.isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | }).isRequired, 15 | setPosition: PropTypes.func.isRequired, 16 | setVisibleOn: PropTypes.func.isRequired, 17 | } 18 | 19 | shouldComponentUpdate(nextProps) { 20 | return !this.props.parameters.equals(nextProps.parameters); 21 | } 22 | 23 | _setPosition = (position) => { 24 | this.props.setPosition('compass', null, parseInt(position, 10)); 25 | } 26 | 27 | render() { 28 | const { 29 | setVisibleOn, 30 | } = this.props; 31 | const { 32 | numberOfPanels, 33 | positionY, 34 | visibleOn, 35 | } = this.props.parameters; 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 45 | 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/config/parameters/Position.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Input from '../../components/Input'; 3 | import Column from '../../components//Column'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | 6 | export default class Position extends Component { 7 | static propTypes = { 8 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 9 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 10 | labelX: PropTypes.string.isRequired, 11 | labelY: PropTypes.string.isRequired, 12 | setPosition: PropTypes.func.isRequired, 13 | xMin: PropTypes.number, 14 | xMax: PropTypes.number, 15 | yMin: PropTypes.number, 16 | yMax: PropTypes.number, 17 | name: PropTypes.string.isRequired, 18 | } 19 | 20 | _onChange(axis, position) { 21 | const name = this.props.name; 22 | const x = axis === 'x' ? parseInt(position, 10) : this.props.positionX.get('value'); 23 | const y = axis === 'y' ? parseInt(position, 10) : this.props.positionY.get('value'); 24 | this.props.setPosition(name, x, y); 25 | } 26 | 27 | render() { 28 | const { labelX, labelY, positionX, positionY, xMin, xMax, yMin, yMax } = this.props; 29 | return ( 30 |
31 | 32 | 35 | 36 | 37 | 40 | 41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/config/settings/HomeDirectionDebugInfo.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | 6 | export default class HomeDirectionDebugInfo extends Component { 7 | static propTypes = { 8 | parameters: ImmutablePropTypes.contains({ 9 | numberOfPanels: PropTypes.number.isRequired, 10 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | }).isRequired, 14 | setPosition: PropTypes.func.isRequired, 15 | setVisibleOn: PropTypes.func.isRequired, 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | return !this.props.parameters.equals(nextProps.parameters); 20 | } 21 | 22 | render() { 23 | const { 24 | setPosition, 25 | setVisibleOn, 26 | } = this.props; 27 | const { 28 | numberOfPanels, 29 | positionX, 30 | positionY, 31 | visibleOn, 32 | } = this.props.parameters; 33 | 34 | return ( 35 | 36 | 39 | 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config/parameters/VisibleOn.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Checkbox from 'react-toolbox/lib/checkbox'; 3 | import Label from '../../components/Label'; 4 | import CustomPropTypes from '../../utils/PropTypes'; 5 | import classNames from 'classnames'; 6 | import styles from './VisibleOn.css'; 7 | 8 | export default class ParameterPanels extends Component { 9 | static propTypes = { 10 | name: PropTypes.string.isRequired, 11 | numberOfPanels: PropTypes.number.isRequired, 12 | setVisibleOn: PropTypes.func.isRequired, 13 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | } 15 | 16 | _onChange(index, value) { 17 | const visibleOn = value ? 18 | this.props.visibleOn.get('value') | Math.pow(2, index) : 19 | this.props.visibleOn.get('value') ^ Math.pow(2, index); 20 | 21 | this.props.setVisibleOn(this.props.name, visibleOn); 22 | } 23 | 24 | render() { 25 | const { numberOfPanels, visibleOn } = this.props; 26 | const classes = classNames({ 27 | modified: visibleOn.get('value') !== visibleOn.get('originalValue'), 28 | }, styles.base); 29 | 30 | return ( 31 |
32 |
33 |
35 |
36 | {[...Array(numberOfPanels)].map((_, i) => 37 | 38 | 43 | 44 | )} 45 |
46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/preview/FlightMode.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import StringPreview from './StringPreview'; 3 | 4 | export default class FlightMode extends StringPreview { 5 | static propTypes = { 6 | ...StringPreview.propTypes, 7 | vehicleType: PropTypes.number.isRequired, 8 | flightMode: PropTypes.number.isRequired, 9 | } 10 | 11 | content() { 12 | const { flightMode, vehicleType } = this.props; 13 | 14 | switch (vehicleType) { 15 | case 0: 16 | switch (flightMode) { 17 | case 0: return 'STAB'; 18 | case 1: return 'ACRO'; 19 | case 2: return 'ALTH'; 20 | case 3: return 'AUTO'; 21 | case 4: return 'GUID'; 22 | case 5: return 'LOIT'; 23 | case 6: return 'RETL'; 24 | case 7: return 'CIRC'; 25 | case 8: return 'POSI'; 26 | case 9: return 'LAND'; 27 | case 10: return 'OFLO'; 28 | case 11: return 'DRIF'; 29 | case 13: return 'SPRT'; 30 | case 14: return 'FLIP'; 31 | case 15: return 'ATUN'; 32 | case 16: return 'POSH'; 33 | case 17: return 'BRAK'; 34 | default: return 'unknown'; 35 | } 36 | case 1: 37 | switch (flightMode) { 38 | case 0: return 'MANU'; 39 | case 1: return 'CIRC'; 40 | case 2: return 'STAB'; 41 | case 3: return 'TRNG'; 42 | case 4: return 'ACRO'; 43 | case 5: return 'FBWA'; 44 | case 6: return 'FBWB'; 45 | case 7: return 'CRUI'; 46 | case 8: return 'ATUN'; 47 | case 10: return 'AUTO'; 48 | case 11: return 'RETL'; 49 | case 12: return 'LOIT'; 50 | case 15: return 'GUID'; 51 | case 16: return 'INIT'; 52 | default: return 'unknown'; 53 | } 54 | default: return 'unknown'; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import AppBar from 'react-toolbox/lib/app_bar'; 3 | import { Layout, NavDrawer, Panel, List, ListItem, IconButton } from 'react-toolbox'; 4 | import styles from './App.css'; 5 | 6 | export default class App extends Component { 7 | 8 | static propTypes = { 9 | children: PropTypes.element.isRequired 10 | }; 11 | 12 | constructor(props) { 13 | super(props); 14 | 15 | this.state = { 16 | showDrawer: false, 17 | }; 18 | } 19 | 20 | _toggleDrawer = () => { 21 | this.setState({ ...this.state, showDrawer: !this.state.showDrawer }); 22 | } 23 | 24 | render() { 25 | return ( 26 | 27 | 28 | 29 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | PlayUAV OSD Configurator 47 | 48 |
49 | {this.props.children} 50 |
51 | { 52 | (() => { 53 | if (process.env.NODE_ENV !== 'production') { 54 | const DevTools = require('./DevTools'); 55 | return ; 56 | } 57 | })() 58 | } 59 |
60 |
61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/config/Config.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ParametersModule from './ParametersModule'; 3 | import Snackbar from 'react-toolbox/lib/snackbar'; 4 | import Sidebar from './Sidebar'; 5 | import Preview from '../preview/Preview'; 6 | 7 | 8 | export default class Index extends Component { 9 | state = { 10 | errorMessage: '', 11 | infoMessage: '', 12 | } 13 | 14 | _showErrorMessage = (error) => { 15 | this.setState({ ...this.state, errorMessage: error }); 16 | } 17 | 18 | _showInfoMessage = (message) => { 19 | this.setState({ ...this.state, infoMessage: message }); 20 | } 21 | 22 | _closeInfoSnackbar = () => { 23 | this.setState({ ...this.state, infoMessage: '' }); 24 | } 25 | 26 | _closeErrorSnackbar = () => { 27 | this.setState({ ...this.state, errorMessage: '' }); 28 | } 29 | 30 | render() { 31 | return ( 32 |
33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 53 | 61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/config/settings/Time.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import SimpleSettings from './SimpleSettings'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Time extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | numberOfPanels: PropTypes.number.isRequired, 13 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | type: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 17 | }).isRequired, 18 | setFontSize: PropTypes.func.isRequired, 19 | setHAlignment: PropTypes.func.isRequired, 20 | setPosition: PropTypes.func.isRequired, 21 | setType: PropTypes.func.isRequired, 22 | setVisibleOn: PropTypes.func.isRequired, 23 | } 24 | 25 | shouldComponentUpdate(nextProps) { 26 | return !this.props.parameters.equals(nextProps.parameters); 27 | } 28 | 29 | _setType = (type) => { 30 | this.props.setType('time', type); 31 | } 32 | 33 | render() { 34 | const { type } = this.props.parameters; 35 | const typeOptions = [ 36 | { value: 0, label: 'power on' }, 37 | { value: 1, label: 'last heartbeat' }, 38 | { value: 2, label: 'armed' }, 39 | ]; 40 | 41 | return ( 42 | 43 | 46 | 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/data/icons/medium.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | 3 | const shape = [ 4 | [0x0, 0x0, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x38, 0x7c, 0xfe, 0x0, 0x0, 0x0], // GPS 5 | [0x0, 0x0, 0x0, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0], // HDOP 6 | [0x0, 0x0, 0x0, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0], // TIME 7 | [0xf8, 0xf8, 0xfe, 0xff, 0xff, 0xef, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0x0], // WP_DISTANCE 8 | [0x7e, 0xff, 0xfe, 0x70, 0x70, 0x38, 0x1c, 0x1c, 0x38, 0x70, 0x70, 0xfe, 0xff, 0x7e], // TOTAL_TRIP 9 | [0xe0, 0xe0, 0xe0, 0xf8, 0xf8, 0xf8, 0xf8, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe], // RSSI 10 | [0x10, 0x38, 0x38, 0x7c, 0x1fe, 0x7c, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x10], // LINK_QUALITY 11 | [0x0, 0x0, 0x0, 0x24, 0x7e, 0xff, 0xff, 0xff, 0x7e, 0x24, 0x0, 0x0, 0x0, 0x0], // HOME_DISTANCE 12 | ]; 13 | 14 | const outline = [ 15 | [0x0, 0x0, 0xc7, 0xbb, 0x7d, 0x6d, 0x7d, 0xbb, 0xc7, 0xd7, 0xbb, 0x1, 0x0, 0x0], // GPS 16 | [0x0, 0x0, 0x0, 0xc7, 0xab, 0x6d, 0x1, 0x6d, 0xab, 0xc7, 0x0, 0x0, 0x0, 0x0], // HDOP 17 | [0x0, 0x0, 0x0, 0xc7, 0xab, 0x6d, 0x61, 0x7d, 0xbb, 0xc7, 0x0, 0x0, 0x0, 0x0], // TIME 18 | [0x7, 0x57, 0x51, 0x54, 0x45, 0x50, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x1f], // WP_DISTANCE 19 | [0x81, 0x7e, 0x41, 0xaf, 0xaf, 0xd7, 0xeb, 0xeb, 0xd7, 0xaf, 0xaf, 0x41, 0x7e, 0x81], // TOTAL_TRIP 20 | [0x1f, 0x5f, 0x5f, 0x47, 0x57, 0x57, 0x57, 0x51, 0x55, 0x55, 0x55, 0x55, 0x55, 0x0], // RSSI 21 | [0xee, 0xd6, 0xd6, 0x92, 0x55, 0xba, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xee], // LINK_QUALITY 22 | [0x80, 0x80, 0x80, 0xdb, 0xa5, 0x42, 0x7e, 0x42, 0xa5, 0xdb, 0x80, 0x80, 0x80, 0x80], // HOME_DISTANCE 23 | ]; 24 | 25 | const dimensions = { width: 8, height: 14 }; 26 | 27 | function getData(index) { 28 | return { shape: shape[index], outline: outline[index].map((b) => (~b >>> 0) & Math.pow(2, dimensions.width) - 1) }; 29 | } 30 | 31 | export default { dimensions, getData }; 32 | -------------------------------------------------------------------------------- /src/utils/units.js: -------------------------------------------------------------------------------- 1 | const speedMultiplier = [3.6, 2.23]; 2 | const speedUnitsLong = ['KM/H', 'Mi/H']; 3 | const distanceMultiplyer = [1.0, 3.28]; 4 | const distanceDivider = [1000, 5280]; 5 | const unitsShort = ['M', 'F']; 6 | const unitsLong = ['KM', 'Mi']; 7 | 8 | function longDistanceUnits(units) { 9 | return unitsLong[units]; 10 | } 11 | 12 | function shortDistanceUnits(units) { 13 | return unitsShort[units]; 14 | } 15 | 16 | function speedUnits(units) { 17 | return speedUnitsLong[units]; 18 | } 19 | 20 | function convertSpeed(speed, units) { 21 | const unitsString = speedUnitsLong[units]; 22 | const convertedSpeed = convertSpeedWithoutUnits(speed, units); 23 | 24 | return `${convertedSpeed}${unitsString}`; 25 | } 26 | 27 | function convertSpeedWithoutUnits(speed, units) { 28 | const convert = speedMultiplier[units]; 29 | return speed * convert; 30 | } 31 | 32 | function convertDistance(distance, units) { 33 | const convertedDistance = convertDistanceWithoutUnits(distance, units); 34 | const divider = distanceDivider[units]; 35 | let unitsString = ''; 36 | let distanceString = ''; 37 | 38 | if (distance < divider) { 39 | unitsString = unitsShort[units]; 40 | distanceString = convertedDistance.toFixed(0); 41 | } else { 42 | unitsString = unitsLong[units]; 43 | distanceString = convertedDistance.toFixed(2); 44 | } 45 | 46 | return `${distanceString}${unitsString}`; 47 | } 48 | 49 | function convertDistanceWithoutUnits(distance, units) { 50 | const multiplier = distanceMultiplyer[units]; 51 | const divider = distanceDivider[units]; 52 | 53 | const correctedDistance = distance * multiplier; 54 | 55 | if (correctedDistance >= divider) { 56 | return correctedDistance / divider; 57 | } 58 | return correctedDistance; 59 | } 60 | 61 | export default { 62 | convertDistance, 63 | convertSpeed, 64 | convertDistanceWithoutUnits, 65 | convertSpeedWithoutUnits, 66 | longDistanceUnits, 67 | shortDistanceUnits, 68 | speedUnits 69 | }; 70 | -------------------------------------------------------------------------------- /src/pixler/actions.js: -------------------------------------------------------------------------------- 1 | export const EMPTY = 'pixler/empty'; 2 | export const SHAPE = 'pixler/shape'; 3 | export const OUTLINE = 'pixler/outline'; 4 | export const CLEAR = 'pixler/clear'; 5 | export const INVERT_OUTLINE = 'pixler/invert_outline'; 6 | export const LOAD_CHARACTER = 'pixler/load_character'; 7 | export const LOAD_ICON = 'pixler/load_icon'; 8 | export const MIRROR = 'pixler/mirror'; 9 | export const SET_FONT_SIZE = 'pixler/set_font_size'; 10 | export const SET_OUTLINE = 'pixler/set_outline'; 11 | export const SET_PIXEL = 'pixler/set_pixel'; 12 | export const SET_SHAPE = 'pixler/set_shape'; 13 | export const SHIFT_DOWN = 'pixler/shift_down'; 14 | export const SHIFT_LEFT = 'pixler/shift_left'; 15 | export const SHIFT_RIGHT = 'pixler/shift_right'; 16 | export const SHIFT_UP = 'pixler/shift_up'; 17 | 18 | export function clear() { 19 | return { type: CLEAR }; 20 | } 21 | 22 | export function invertOutline() { 23 | return { type: INVERT_OUTLINE }; 24 | } 25 | 26 | export function loadCharacter(character) { 27 | return { type: LOAD_CHARACTER, payload: character }; 28 | } 29 | 30 | export function loadIcon(icon) { 31 | return { type: LOAD_ICON, payload: icon }; 32 | } 33 | 34 | export function mirror() { 35 | return { type: MIRROR }; 36 | } 37 | 38 | export function setFontSize(fontSize) { 39 | return { type: SET_FONT_SIZE, payload: fontSize }; 40 | } 41 | 42 | export function setOutline(outline) { 43 | return { type: SET_OUTLINE, payload: outline }; 44 | } 45 | 46 | export function setPixel(row, column, pixelType) { 47 | return { type: SET_PIXEL, column, row, pixelType }; 48 | } 49 | 50 | export function setShape(shape) { 51 | return { type: SET_SHAPE, payload: shape }; 52 | } 53 | 54 | export function shiftDown() { 55 | return { type: SHIFT_DOWN }; 56 | } 57 | 58 | export function shiftLeft() { 59 | return { type: SHIFT_LEFT }; 60 | } 61 | 62 | export function shiftRight() { 63 | return { type: SHIFT_RIGHT }; 64 | } 65 | 66 | export function shiftUp() { 67 | return { type: SHIFT_UP }; 68 | } 69 | -------------------------------------------------------------------------------- /src/config/settings/ClimbRate.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class ClimbRate extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | numberOfPanels: PropTypes.number.isRequired, 12 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | }).isRequired, 16 | setFontSize: PropTypes.func.isRequired, 17 | setVisibleOn: PropTypes.func.isRequired, 18 | setPosition: PropTypes.func.isRequired, 19 | } 20 | 21 | shouldComponentUpdate(nextProps) { 22 | return !this.props.parameters.equals(nextProps.parameters); 23 | } 24 | 25 | render() { 26 | const { 27 | setFontSize, 28 | setPosition, 29 | setVisibleOn, 30 | } = this.props; 31 | const { 32 | fontSize, 33 | numberOfPanels, 34 | positionX, 35 | positionY, 36 | visibleOn, 37 | } = this.props.parameters; 38 | 39 | return ( 40 | 41 | 44 | 45 | 46 | 47 | 50 | 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/preview/HomeDirectionDebugInfo.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import * as Canvas_types from '../utils/Canvas'; 3 | import Canvas from '../utils/Canvas'; 4 | import PreviewBase from './PreviewBase'; 5 | import fonts from '../utils/fonts'; 6 | 7 | export default class HomeDirectionDebugInfo extends PreviewBase { 8 | static propTypes = { 9 | panel: PropTypes.number.isRequired, 10 | positionX: PropTypes.number.isRequired, 11 | positionY: PropTypes.number.isRequired, 12 | visibleOn: PropTypes.number.isRequired, 13 | } 14 | 15 | draw() { 16 | const font = fonts.getFont(0); 17 | this.canvas.clear(); 18 | const height = this.refs.canvas.height; 19 | 20 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 21 | var debugLines = [ 22 | 'abs h.b. 68', 23 | 'rel h.b. -7', 24 | 'comp. b 75' 25 | ]; 26 | var currentLineYOffset = 0; 27 | for (let debugLineIndex = 0; debugLineIndex < debugLines.length; debugLineIndex++) { 28 | // Get the current line 29 | var currentDebugLine = debugLines[debugLineIndex]; 30 | // Draw the current line 31 | var debugLineStringPosition = Canvas.calculateStringPosition(currentDebugLine, 0, currentLineYOffset, Canvas_types.H_ALIGNMENT_LEFT, Canvas_types.V_ALIGNMENT_TOP, font); 32 | this.canvas.drawString(currentDebugLine, 0, currentLineYOffset, font); 33 | // Move Y position down one line 34 | currentLineYOffset += debugLineStringPosition.height; 35 | } 36 | } 37 | } 38 | 39 | render() { 40 | const { positionX, positionY } = this.props; 41 | const height = 90; 42 | const width = 300; 43 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 44 | 45 | return ( 46 | !visible ? 47 | : 48 | 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/config/settings/Video.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Input from '../../components/Input'; 4 | import Parameters from '../parameters'; 5 | import Column from '../../components/Column'; 6 | import CustomPropTypes from '../../utils/PropTypes'; 7 | 8 | export default class Video extends Component { 9 | static propTypes = { 10 | parameters: ImmutablePropTypes.contains({ 11 | maxPanels: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | offsetX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | offsetY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | units: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | videoMode: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setMaxPanels: PropTypes.func.isRequired, 18 | setOffset: PropTypes.func.isRequired, 19 | setUnits: PropTypes.func.isRequired, 20 | setMode: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | render() { 28 | const { setMode, setUnits, setMaxPanels, setOffset } = this.props; 29 | const { videoMode, units, offsetX, offsetY, maxPanels } = this.props.parameters; 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 50 | 51 | 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/pixler/Editor.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Pixel from './Pixel'; 3 | import { EMPTY, OUTLINE, SHAPE } from './actions'; 4 | import styles from './Editor.css'; 5 | import fonts from '../utils/fonts'; 6 | import { Card, CardText, CardTitle } from 'react-toolbox/lib/card'; 7 | 8 | export default class Editor extends Component { 9 | static propTypes = { 10 | setPixel: PropTypes.func.isRequired, 11 | outline: PropTypes.arrayOf(PropTypes.number).isRequired, 12 | shape: PropTypes.arrayOf(PropTypes.number).isRequired, 13 | fontSize: PropTypes.number.isRequired, 14 | } 15 | 16 | _setPixel = (row, column, type) => { 17 | this.props.setPixel(row, column, type); 18 | } 19 | 20 | render() { 21 | const { height, width } = fonts.getFont(this.props.fontSize).dimensions; 22 | 23 | const pixels = [...Array(height)].map((_, row) => { 24 | const shape = this.props.shape[row]; 25 | const outline = this.props.outline[row]; 26 | 27 | const rowPixels = [...Array(width)].map((__, i) => { 28 | const column = width - i - 1; 29 | const key = `${row}${column}`; 30 | let type = null; 31 | if (shape & outline & Math.pow(2, column)) { 32 | type = OUTLINE; 33 | } else if (shape & Math.pow(2, column)) { 34 | type = SHAPE; 35 | } else { 36 | type = EMPTY; 37 | } 38 | 39 | return (); 40 | }); 41 | 42 | return ( 43 |
44 | {rowPixels} 45 |
{row + 1}
46 |
47 | ); 48 | }); 49 | 50 | const columnNumbers = [...Array(width)].map((_, i) => ( 51 |
{i + 1}
52 | )); 53 | 54 | return ( 55 | 56 | 60 | 61 |
62 | {columnNumbers} 63 |
64 | {pixels} 65 |
66 |
67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/preview/Wind.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | import units from '../utils/units'; 6 | 7 | export default class Wind extends PreviewBase { 8 | static propTypes = { 9 | panel: PropTypes.number.isRequired, 10 | positionX: PropTypes.number.isRequired, 11 | positionY: PropTypes.number.isRequired, 12 | units: PropTypes.number.isRequired, 13 | visibleOn: PropTypes.number.isRequired, 14 | windDirection: PropTypes.number.isRequired, 15 | windSpeed: PropTypes.number.isRequired, 16 | } 17 | 18 | draw() { 19 | this.canvas.clear(); 20 | const height = this.refs.canvas.height; 21 | const posY = height / 2; 22 | const font = fonts.getFont(0); 23 | 24 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 25 | const windString = units.convertSpeed(this.props.windSpeed, this.props.units); 26 | const windPosition = Canvas.calculateStringPosition(windString, 20, posY, 0, 1, font); 27 | this.canvas.drawString(windString, windPosition.left, windPosition.top, font); 28 | 29 | this.canvas.save(); 30 | this.canvas.translate(9, 9); 31 | this.canvas.rotate(this.props.windDirection * Math.PI / 180); 32 | this.canvas.translate(-9, -9); 33 | this.canvas.drawLine(6, 7, 9, 1); 34 | this.canvas.drawLine(9, 1, 12, 7); 35 | this.canvas.drawLine(9, 16, 9, 7, true); 36 | this.canvas.restore(); 37 | } 38 | } 39 | 40 | render() { 41 | const { positionX, positionY } = this.props; 42 | const font = fonts.getFont(0); 43 | const windString = units.convertSpeed(this.props.windSpeed, this.props.units); 44 | const windPosition = Canvas.calculateStringPosition(windString, 0, 0, 0, 1, font); 45 | const width = windPosition.width + 20; 46 | const height = 18; 47 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 48 | 49 | return ( 50 | !visible ? 51 | : 52 | 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/preview/StringPreview.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | import icons from '../utils/icons'; 6 | 7 | export default class StringPreview extends PreviewBase { 8 | static propTypes = { 9 | fontSize: PropTypes.number.isRequired, 10 | hAlignment: PropTypes.number.isRequired, 11 | panel: PropTypes.number.isRequired, 12 | positionX: PropTypes.number.isRequired, 13 | positionY: PropTypes.number.isRequired, 14 | setPosition: PropTypes.func.isRequired, 15 | vAlignment: PropTypes.number, 16 | visibleOn: PropTypes.number.isRequired, 17 | } 18 | 19 | static defaultProps = { 20 | vAlignment: 0, 21 | } 22 | 23 | icon() { 24 | return null; 25 | } 26 | 27 | content() { 28 | return ''; 29 | } 30 | 31 | draw() { 32 | const font = fonts.getFont(this.props.fontSize); 33 | const content = this.content(); 34 | this.canvas.clear(); 35 | 36 | if (content && (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 37 | const icon = this.icon(); 38 | const hasIcon = icon !== null; 39 | const x = hasIcon ? font.dimensions.width + 2 : 0; 40 | if (hasIcon) { 41 | const iconFont = icons.getFont(this.props.fontSize); 42 | this.canvas.drawCharacter(icon, 0, 0, iconFont); 43 | } 44 | this.canvas.drawString(content, x, 0, font); 45 | } 46 | } 47 | 48 | render() { 49 | const { hAlignment, positionX, positionY, vAlignment } = this.props; 50 | const content = this.content(); 51 | const font = fonts.getFont(this.props.fontSize); 52 | const hasIcon = this.icon() !== null; 53 | const position = Canvas.calculateStringPosition( 54 | content, positionX, positionY, hAlignment, vAlignment, font, 0, hasIcon); 55 | 56 | const visible = this.content() && 57 | (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 58 | 59 | return ( 60 | !visible ? 61 | : 62 | 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/util/fonts.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import fonts from '../../src/utils/fonts'; 3 | 4 | describe('fonts', () => { 5 | describe('small', () => { 6 | it('should be loadable', () => { 7 | fonts.getFont(0); 8 | }); 9 | 10 | it('should have proper dimensions', () => { 11 | const font = fonts.getFont(0); 12 | expect(font.dimensions).to.have.all.keys('height', 'width'); 13 | }); 14 | 15 | it('should have proper character data', () => { 16 | const font = fonts.getFont(0); 17 | const character = font.getData('a'.charCodeAt(0)); 18 | expect(character).to.have.all.keys('outline', 'shape'); 19 | expect(character.outline.length).to.equal(10); 20 | expect(character.shape.length).to.equal(10); 21 | }); 22 | }); 23 | 24 | describe('medium', () => { 25 | it('should be loadable', () => { 26 | fonts.getFont(1); 27 | }); 28 | 29 | it('should have proper dimensions', () => { 30 | const font = fonts.getFont(1); 31 | expect(font.dimensions).to.have.all.keys('height', 'width'); 32 | }); 33 | 34 | it('should have proper character data', () => { 35 | const font = fonts.getFont(1); 36 | const character = font.getData('a'.charCodeAt(0)); 37 | expect(character).to.have.all.keys('outline', 'shape'); 38 | expect(character.outline.length).to.equal(14); 39 | expect(character.shape.length).to.equal(14); 40 | }); 41 | }); 42 | 43 | describe('large', () => { 44 | it('should be loadable', () => { 45 | fonts.getFont(2); 46 | }); 47 | 48 | it('should have proper dimensions', () => { 49 | const font = fonts.getFont(2); 50 | expect(font.dimensions).to.have.all.keys('height', 'width'); 51 | }); 52 | 53 | it('should have proper character data', () => { 54 | const font = fonts.getFont(2); 55 | const character = font.getData('a'.charCodeAt(0)); 56 | expect(character).to.have.all.keys('outline', 'shape'); 57 | expect(character.outline.length).to.equal(18); 58 | expect(character.shape.length).to.equal(18); 59 | }); 60 | }); 61 | 62 | describe('invalid', () => { 63 | it('should throw an error', () => { 64 | const errorMessage = /trying to get unsupported font for size/; 65 | expect(fonts.getFont).to.throw(Error, errorMessage); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/preview/index.js: -------------------------------------------------------------------------------- 1 | import AbsoluteAltitude from './AbsoluteAltitude'; 2 | import Alarms from './Alarms'; 3 | import AltitudeScale from './AltitudeScale'; 4 | import ArmState from './ArmState'; 5 | import ArtificialHorizon from './ArtificialHorizon'; 6 | import BatteryConsumed from './BatteryConsumed'; 7 | import BatteryCurrent from './BatteryCurrent'; 8 | import BatteryRemaining from './BatteryRemaining'; 9 | import BatteryVoltage from './BatteryVoltage'; 10 | import ClimbRate from './ClimbRate'; 11 | import Compass from './Compass'; 12 | import Efficiency from './Efficiency'; 13 | import FlightMode from './FlightMode'; 14 | import HomeLatitude from './HomeLatitude'; 15 | import HomeLongitude from './HomeLongitude'; 16 | import GpsHdop from './GpsHdop'; 17 | import GpsLatitude from './GpsLatitude'; 18 | import GpsLongitude from './GpsLongitude'; 19 | import GpsStatus from './GpsStatus'; 20 | import HomeDirection from './HomeDirection'; 21 | import HomeDirectionDebugInfo from './HomeDirectionDebugInfo'; 22 | import HomeDistance from './HomeDistance'; 23 | import LinkQuality from './LinkQuality'; 24 | import RCChannels from './RCChannels'; 25 | import Radar from './Radar'; 26 | import RelativeAltitude from './RelativeAltitude'; 27 | import Rssi from './Rssi'; 28 | import SpeedAir from './SpeedAir'; 29 | import SpeedGround from './SpeedGround'; 30 | import SpeedScale from './SpeedScale'; 31 | import Throttle from './Throttle'; 32 | import Time from './Time'; 33 | import TotalTrip from './TotalTrip'; 34 | import VarioGraph from './VarioGraph'; 35 | import Watt from './Watt'; 36 | import Wind from './Wind'; 37 | import WpDistance from './WpDistance'; 38 | 39 | export default { 40 | AbsoluteAltitude, 41 | Alarms, 42 | AltitudeScale, 43 | ArmState, 44 | ArtificialHorizon, 45 | BatteryConsumed, 46 | BatteryCurrent, 47 | BatteryRemaining, 48 | BatteryVoltage, 49 | ClimbRate, 50 | Compass, 51 | Efficiency, 52 | FlightMode, 53 | HomeLatitude, 54 | HomeLongitude, 55 | GpsHdop, 56 | GpsLatitude, 57 | GpsLongitude, 58 | GpsStatus, 59 | HomeDirection, 60 | HomeDirectionDebugInfo, 61 | HomeDistance, 62 | LinkQuality, 63 | RCChannels, 64 | Radar, 65 | RelativeAltitude, 66 | Rssi, 67 | SpeedAir, 68 | SpeedGround, 69 | SpeedScale, 70 | Throttle, 71 | Time, 72 | TotalTrip, 73 | VarioGraph, 74 | Watt, 75 | Wind, 76 | WpDistance, 77 | }; 78 | -------------------------------------------------------------------------------- /src/config/settings/AltitudeScale.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class AltitudeScale extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | numberOfPanels: PropTypes.number.isRequired, 11 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | scaleAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | scaleType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setPosition: PropTypes.func.isRequired, 18 | setScaleAlignment: PropTypes.func.isRequired, 19 | setScaleType: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | render() { 28 | const { 29 | setPosition, setScaleAlignment, setScaleType, setVisibleOn 30 | } = this.props; 31 | 32 | const { 33 | numberOfPanels, positionX, positionY, scaleAlignment, scaleType, visibleOn 34 | } = this.props.parameters; 35 | 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 49 | 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Playuav OSD Configurator 2 | 3 | A simple cross platform tool the playuav osd. 4 | 5 | 6 | [![Build Status](https://travis-ci.org/TobiasBales/PlayuavOSDConfigurator.svg?branch=master)](https://travis-ci.org/TobiasBales/PlayuavOSDConfigurator) 7 | [![Coverage Status](https://coveralls.io/repos/github/TobiasBales/PlayuavOSDConfigurator/badge.svg?branch=master)](https://coveralls.io/github/TobiasBales/PlayuavOSDConfigurator?branch=master) 8 | [![Code Climate](https://codeclimate.com/github/TobiasBales/PlayuavOSDConfigurator/badges/gpa.svg)](https://codeclimate.com/github/TobiasBales/PlayuavOSDConfigurator) 9 | 10 | ## What does it look like? 11 | ![](preview.png) 12 | 13 | ## Releases 14 | 15 | Can be found in the [chrome webstore](https://chrome.google.com/webstore/detail/playuav-osd-configurator/clledgfbcikcmblfhbkhjeoebioekcnb) 16 | 17 | ## Features 18 | 19 | #### Config 20 | The part to configure the osd 21 | * change settings 22 | * flash firmware 23 | * write/read settings to osd 24 | * write/read settings to file 25 | * reset settings to default 26 | * load settings from default.conf on startup (located in same directory as executable) 27 | 28 | #### Pixler 29 | Helper to make new icons/characters 30 | * Create glyphs for small/medium/large fonts 31 | * mirror image 32 | * invert mask (black/white handling) 33 | * move pixels around in frame 34 | 35 | ## Local setup 36 | 37 | ### Installing node 38 | The prefered way is using [ndenv](https://github.com/riywo/ndenv) and running node `5.9.0` 39 | 40 | 41 | ### Development 42 | - `npm install` 43 | - `npm start` 44 | - enable [chrome extension developer mode](chrome://extensions/) 45 | - load unpacked extension from `./src` 46 | 47 | 48 | ### Commit messages, ci, linting etc 49 | - Commit messages should follow the [angular commit message format](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) 50 | - Pull requests will be automatically built on circle ci 51 | - Run eslint against your changes (npm run lint) 52 | 53 | ## Warning 54 | This software comes with no guarantees, it has worked great for me so far but if you blow something up or brick your board, I might be willing to help you but no guarantees. 55 | 56 | ## Special thanks 57 | Special thanks go to the people who made/make the osd software/hardware and also the amazing people who made the electron-react-boilerplate which made the start of this project so much easier! 58 | -------------------------------------------------------------------------------- /src/config/settings/SpeedScale.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class SpeedScale extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | numberOfPanels: PropTypes.number.isRequired, 11 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | scaleAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | scaleType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setPosition: PropTypes.func.isRequired, 18 | setScaleAlignment: PropTypes.func.isRequired, 19 | setScaleType: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | render() { 28 | const { 29 | setPosition, setScaleAlignment, setScaleType, setVisibleOn 30 | } = this.props; 31 | const { 32 | numberOfPanels, positionX, positionY, scaleAlignment, scaleType, visibleOn 33 | } = this.props.parameters; 34 | 35 | return ( 36 | 37 | 38 | 41 | 42 | 43 | 46 | 47 | 50 | 53 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/config/settings/Attitude3d.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Attitude3d extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | mapRadius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | numberOfPanels: PropTypes.number.isRequired, 12 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | scale: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setPosition: PropTypes.func.isRequired, 18 | setRadius: PropTypes.func.isRequired, 19 | setScale: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | render() { 28 | const { 29 | setPosition, 30 | setRadius, 31 | setScale, 32 | setVisibleOn, 33 | } = this.props; 34 | const { 35 | numberOfPanels, 36 | positionX, 37 | positionY, 38 | mapRadius, 39 | scale, 40 | visibleOn, 41 | } = this.props.parameters; 42 | 43 | return ( 44 | 45 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 59 | 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/preview/HomeDirection.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import PreviewBase from './PreviewBase'; 3 | 4 | export default class Wind extends PreviewBase { 5 | static propTypes = { 6 | heading: PropTypes.number.isRequired, 7 | homeBearing: PropTypes.number.isRequired, 8 | panel: PropTypes.number.isRequired, 9 | positionX: PropTypes.number.isRequired, 10 | positionY: PropTypes.number.isRequired, 11 | visibleOn: PropTypes.number.isRequired, 12 | } 13 | 14 | draw() { 15 | this.canvas.clear(); 16 | const height = this.refs.canvas.height; 17 | 18 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 19 | const { homeBearing, heading } = this.props; 20 | const bearing = homeBearing - heading; 21 | 22 | this.canvas.save(); 23 | this.canvas.translate(height / 2, height / 2); 24 | this.canvas.rotate(bearing * Math.PI / 180); 25 | 26 | this.canvas.drawLine(-2, -2, -2, 8); 27 | this.canvas.drawLine(-1, -2, -1, 8); 28 | this.canvas.drawLine(0, -2, 0, 8); 29 | this.canvas.drawLine(1, -2, 1, 8); 30 | this.canvas.drawLine(2, -2, 2, 8); 31 | this.canvas.drawLine(-5, -3, 5, -3); 32 | this.canvas.drawLine(-4, -4, 4, -4); 33 | this.canvas.drawLine(-3, -5, 3, -5); 34 | this.canvas.drawLine(-2, -6, 2, -6); 35 | this.canvas.drawLine(-1, -7, 1, -7); 36 | this.canvas.drawLine(0, -8, 0, -8); 37 | 38 | this.canvas.drawLine(-7, -2, 0, -9, false, true); 39 | this.canvas.drawLine(0, -9, 7, -2, false, true); 40 | this.canvas.drawLine(7, -2, 3, -2, false, true); 41 | this.canvas.drawLine(-7, -2, -3, -2, false, true); 42 | this.canvas.drawLine(3, -2, 3, 9, false, true); 43 | this.canvas.drawLine(-3, -2, -3, 9, false, true); 44 | this.canvas.drawLine(-3, 9, 3, 9, false, true); 45 | this.canvas.restore(); 46 | } 47 | } 48 | 49 | render() { 50 | const { positionX, positionY } = this.props; 51 | const height = 20; 52 | const width = 20; 53 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 54 | 55 | return ( 56 | !visible ? 57 | : 58 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/eventPage.js: -------------------------------------------------------------------------------- 1 | function startApplication() { 2 | const applicationStartTime = new Date().getTime(); 3 | 4 | chrome.app.window.create('main.html', { 5 | id: 'main-window', 6 | frame: 'chrome', 7 | state: 'maximized', 8 | innerBounds: { 9 | minWidth: 700, 10 | minHeight: 625 11 | } 12 | }, (createdWindow) => { 13 | createdWindow.contentWindow.addEventListener('load', () => { 14 | // createdWindow.contentWindow.catch_startup_time(applicationStartTime); 15 | }); 16 | 17 | createdWindow.onClosed.addListener(() => { 18 | // autoamtically close the port when application closes 19 | // save connectionId in separate variable before createdWindow.contentWindow is destroyed 20 | const connectionId = createdWindow.contentWindow.serial.connectionId; 21 | 22 | if (connectionId) { 23 | chrome.serial.disconnect(connectionId, (result) => { 24 | console.log(`SERIAL: Connection closed - ${result}`); 25 | }); 26 | } 27 | }); 28 | }); 29 | } 30 | 31 | chrome.app.runtime.onLaunched.addListener(startApplication); 32 | 33 | chrome.runtime.onInstalled.addListener((details) => { 34 | if (details.reason === 'update') { 35 | const previousVersionArr = details.previousVersion.split('.'); 36 | const currentVersionArr = chrome.runtime.getManifest().version.split('.'); 37 | 38 | if (currentVersionArr[0] > previousVersionArr[0]) { 39 | chrome.storage.local.get('update_notify', (result) => { 40 | if (result.update_notify === 'undefined' || result.update_notify) { 41 | const manifest = chrome.runtime.getManifest(); 42 | const message = chrome.i18n.getMessage( 43 | 'notifications_app_just_updated_to_version', [manifest.version]); 44 | const buttonTitle = chrome.i18n.getMessage('notifications_click_here_to_start_app'); 45 | const options = { 46 | priority: 0, 47 | type: 'basic', 48 | title: manifest.name, 49 | message, 50 | iconUrl: '/images/icon_128.png', 51 | buttons: [{ title: buttonTitle }] 52 | }; 53 | 54 | chrome.notifications.create('baseflight_update', options, () => { 55 | // empty 56 | }); 57 | } 58 | }); 59 | } 60 | } 61 | }); 62 | 63 | chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => { 64 | if (notificationId === 'baseflight_update') { 65 | startApplication(); 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/config/settings/Map.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Map extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | numberOfPanels: PropTypes.number.isRequired, 13 | radius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | vAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setFontSize: PropTypes.func.isRequired, 18 | setHAlignment: PropTypes.func.isRequired, 19 | setRadius: PropTypes.func.isRequired, 20 | setVAlignment: PropTypes.func.isRequired, 21 | setVisibleOn: PropTypes.func.isRequired, 22 | } 23 | 24 | shouldComponentUpdate(nextProps) { 25 | return !this.props.parameters.equals(nextProps.parameters); 26 | } 27 | 28 | render() { 29 | const { 30 | fontSize, radius, vAlignment, hAlignment, visibleOn, numberOfPanels 31 | } = this.props.parameters; 32 | const { 33 | setFontSize, setRadius, setVAlignment, setHAlignment, setVisibleOn 34 | } = this.props; 35 | 36 | return ( 37 | 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 59 | 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/preview/SpeedScale.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | import units from '../utils/units'; 6 | 7 | export default class SpeedScale extends PreviewBase { 8 | static propTypes = { 9 | speedGround: PropTypes.number.isRequired, 10 | panel: PropTypes.number.isRequired, 11 | positionX: PropTypes.number.isRequired, 12 | positionY: PropTypes.number.isRequired, 13 | scaleAlignment: PropTypes.number.isRequired, 14 | scaleType: PropTypes.number.isRequired, 15 | speedAir: PropTypes.number.isRequired, 16 | units: PropTypes.number.isRequired, 17 | visibleOn: PropTypes.number.isRequired, 18 | } 19 | 20 | draw() { 21 | const font = fonts.getFont(0); 22 | this.canvas.clear(); 23 | 24 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 25 | const { speedAir, speedGround, scaleAlignment } = this.props; 26 | const hAlignment = scaleAlignment === 0 ? 0 : 2; 27 | const prefix = this.props.scaleType === 0 ? 'GS' : 'AS'; 28 | const prefixPosition = Canvas.calculateStringPosition(prefix, 0, 0, hAlignment, 0, font); 29 | const speedUnitString = units.speedUnits(this.props.units); 30 | const unitPosition = Canvas.calculateStringPosition( 31 | speedUnitString, 0, 0, hAlignment, 0, font); 32 | const speed = this.props.scaleType === 0 ? speedGround : speedAir; 33 | const posX = scaleAlignment === 0 ? 0 : 75; 34 | const posY = 50; 35 | const scaleSpeed = units.convertSpeedWithoutUnits(speed, this.props.units); 36 | this.canvas.drawVerticalScale( 37 | Math.round(scaleSpeed, 0), 38 | 60, scaleAlignment, posX, posY, 72, 10, 20, 5, 8, 11, 100, font); 39 | this.canvas.drawString(prefix, posX + prefixPosition.left, posY - 50, font); 40 | this.canvas.drawString(speedUnitString, posX + unitPosition.left, posY + 40, font); 41 | } 42 | } 43 | 44 | render() { 45 | const { positionX, positionY, scaleAlignment } = this.props; 46 | const xOffset = scaleAlignment === 0 ? 0 : 75; 47 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 48 | 49 | return ( 50 | !visible ? 51 | : 52 | 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/config/settings/ArtificialHorizon.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class ArtificialHorizon extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | numberOfPanels: PropTypes.number.isRequired, 11 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | scale: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | type: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setPosition: PropTypes.func.isRequired, 18 | setScale: PropTypes.func.isRequired, 19 | setType: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | _setType = (type) => { 28 | this.props.setType('artificialHorizon', type); 29 | } 30 | 31 | render() { 32 | const { 33 | setPosition, 34 | setScale, 35 | setVisibleOn, 36 | } = this.props; 37 | const { 38 | numberOfPanels, 39 | positionX, 40 | positionY, 41 | scale, 42 | type, 43 | visibleOn, 44 | } = this.props.parameters; 45 | 46 | const typeOptions = [{ value: 0, label: 'mission planner' }, { value: 1, label: 'simple ' }]; 47 | 48 | return ( 49 | 50 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 64 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/preview/AltitudeScale.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | import units from '../utils/units'; 6 | 7 | export default class AltitudeScale extends PreviewBase { 8 | static propTypes = { 9 | absoluteAltitude: PropTypes.number.isRequired, 10 | panel: PropTypes.number.isRequired, 11 | positionX: PropTypes.number.isRequired, 12 | positionY: PropTypes.number.isRequired, 13 | relativeAltitude: PropTypes.number.isRequired, 14 | scaleAlignment: PropTypes.number.isRequired, 15 | scaleType: PropTypes.number.isRequired, 16 | units: PropTypes.number.isRequired, 17 | visibleOn: PropTypes.number.isRequired, 18 | } 19 | 20 | draw() { 21 | const font = fonts.getFont(0); 22 | this.canvas.clear(); 23 | 24 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 25 | const { absoluteAltitude, relativeAltitude, scaleAlignment } = this.props; 26 | const hAlignment = scaleAlignment === 0 ? 0 : 2; 27 | const prefix = this.props.scaleType === 0 ? 'AAlt' : 'Alt'; 28 | const prefixPosition = Canvas.calculateStringPosition(prefix, 0, 0, hAlignment, 0, font); 29 | const shortDistanceUnitString = units.shortDistanceUnits(this.props.units); 30 | const unitPosition = Canvas.calculateStringPosition( 31 | shortDistanceUnitString, 0, 0, hAlignment, 0, font); 32 | const altitude = this.props.scaleType === 0 ? absoluteAltitude : relativeAltitude; 33 | const posX = scaleAlignment === 0 ? 0 : 75; 34 | const posY = 50; 35 | const scaleAltitude = units.convertDistanceWithoutUnits(altitude, this.props.units); 36 | this.canvas.drawVerticalScale( 37 | Math.round(scaleAltitude, 0), 38 | 60, scaleAlignment, posX, posY, 72, 10, 20, 5, 8, 11, 10000, font); 39 | this.canvas.drawString(prefix, posX + prefixPosition.left, posY - 50, font); 40 | this.canvas.drawString(shortDistanceUnitString, posX + unitPosition.left, posY + 40, font); 41 | } 42 | } 43 | 44 | render() { 45 | const { positionX, positionY } = this.props; 46 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 47 | 48 | return ( 49 | !visible ? 50 | : 51 | 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/config/settings/Serial.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | import Input from '../../components/Input'; 7 | 8 | export default class Serial extends Component { 9 | static propTypes = { 10 | parameters: ImmutablePropTypes.contains({ 11 | baudRate: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | fcType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | splashMillisecondsToShowValue: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | }).isRequired, 15 | setBaudRate: PropTypes.func.isRequired, 16 | setFcType: PropTypes.func.isRequired, 17 | setValue: PropTypes.func.isRequired, 18 | } 19 | 20 | shouldComponentUpdate(nextProps) { 21 | return !this.props.parameters.equals(nextProps.parameters); 22 | } 23 | 24 | _setValue(serial) { 25 | return (value) => { 26 | this.props.setValue('serial', serial, parseInt(value, 10)); 27 | }; 28 | } 29 | 30 | render() { 31 | const { baudRate, fcType, splashMillisecondsToShowValue } = this.props.parameters; 32 | const { setBaudRate, setFcType } = this.props; 33 | const fcTypeOptions = [ 34 | { value: 0, label: 'apm/pixhawk' }, { value: 1, label: 'cc3d/revo' } 35 | ]; 36 | 37 | 38 | const baudRateOptions = [ 39 | { value: 1, label: '4800' }, { value: 2, label: '9600' }, 40 | { value: 3, label: '19200' }, { value: 4, label: '38400' }, 41 | { value: 5, label: '43000' }, { value: 6, label: '56000' }, 42 | { value: '7', label: '57600' }, { value: 8, label: '115200' } 43 | ]; 44 | 45 | return ( 46 | 47 | 48 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/preview/ClimbRate.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | 6 | export default class ClimbRate extends PreviewBase { 7 | static propTypes = { 8 | climbRate: PropTypes.number.isRequired, 9 | fontSize: PropTypes.number.isRequired, 10 | panel: PropTypes.number.isRequired, 11 | positionX: PropTypes.number.isRequired, 12 | positionY: PropTypes.number.isRequired, 13 | visibleOn: PropTypes.number.isRequired, 14 | } 15 | 16 | static defaultProps = { 17 | vAlignment: 0, 18 | } 19 | 20 | draw() { 21 | const font = fonts.getFont(this.props.fontSize); 22 | const arrowLength = this.props.fontSize === 0 ? 6 : 8; 23 | this.canvas.clear(); 24 | 25 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 26 | const xPos = 3; 27 | const yPos = this.refs.canvas.height / 2; 28 | this.canvas.drawString(this.content(), xPos + 5, yPos - font.dimensions.height / 2, font); 29 | if (this.props.climbRate > 0) { 30 | this.canvas.drawLine(xPos, yPos - arrowLength, xPos, yPos + arrowLength, true); 31 | this.canvas.drawLine(xPos - 3, yPos - arrowLength + 3, xPos, yPos - arrowLength); 32 | this.canvas.drawLine(xPos + 3, yPos - arrowLength + 3, xPos, yPos - arrowLength); 33 | } else if (this.props.climbRate < 0) { 34 | this.canvas.drawLine(xPos, yPos - arrowLength, xPos, yPos + arrowLength, true); 35 | this.canvas.drawLine(xPos - 3, yPos + arrowLength - 3, xPos, yPos + arrowLength); 36 | this.canvas.drawLine(xPos + 3, yPos + arrowLength - 3, xPos, yPos + arrowLength); 37 | } 38 | } 39 | } 40 | 41 | content() { 42 | return Math.abs(this.props.climbRate).toFixed(2); 43 | } 44 | 45 | render() { 46 | const { positionX, positionY } = this.props; 47 | const hAlignment = 0; 48 | const vAlignment = 1; 49 | const font = fonts.getFont(this.props.fontSize); 50 | const position = Canvas.calculateStringPosition( 51 | this.content(), positionX, positionY, hAlignment, vAlignment, font); 52 | const arrowLength = this.props.fontSize === 0 ? 6 : 8; 53 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 54 | 55 | return ( 56 | !visible ? 57 | : 58 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/config/settings/Radar.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Radar extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | homeRadius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 11 | numberOfPanels: PropTypes.number.isRequired, 12 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | radius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | wpRadius: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 17 | }).isRequired, 18 | setPosition: PropTypes.func.isRequired, 19 | setRadius: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | render() { 28 | const { 29 | setPosition, 30 | setRadius, 31 | setVisibleOn, 32 | } = this.props; 33 | const { 34 | homeRadius, 35 | numberOfPanels, 36 | positionX, 37 | positionY, 38 | radius, 39 | visibleOn, 40 | wpRadius, 41 | } = this.props.parameters; 42 | 43 | return ( 44 | 45 | 48 | 49 | 52 | 53 | 54 | 57 | 58 | 59 | 62 | 63 | 66 | 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/config/settings/SimpleSettings.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class SimpleSettings extends Component { 8 | static propTypes = { 9 | name: PropTypes.string, 10 | label: PropTypes.string, 11 | children: PropTypes.node, 12 | parameters: ImmutablePropTypes.contains({ 13 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | numberOfPanels: PropTypes.number.isRequired, 16 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 17 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 18 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 19 | }).isRequired, 20 | setFontSize: PropTypes.func.isRequired, 21 | setHAlignment: PropTypes.func.isRequired, 22 | setPosition: PropTypes.func.isRequired, 23 | setVisibleOn: PropTypes.func.isRequired, 24 | } 25 | 26 | shouldComponentUpdate(nextProps) { 27 | return !this.props.parameters.equals(nextProps.parameters); 28 | } 29 | 30 | parameterModified(key) { 31 | return this.props.parameters.get(key) !== this.props.parameters.get(`base${key}`); 32 | } 33 | 34 | render() { 35 | const { 36 | children, 37 | setFontSize, 38 | setHAlignment, 39 | setPosition, 40 | setVisibleOn, 41 | } = this.props; 42 | const { 43 | fontSize, 44 | hAlignment, 45 | numberOfPanels, 46 | positionX, 47 | positionY, 48 | visibleOn, 49 | } = this.props.parameters; 50 | 51 | const name = this.props.name || this.name; 52 | const label = this.props.label || this.label || name; 53 | 54 | return ( 55 | 56 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | {children} 68 | 71 | 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/config/settings/Throttle.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import CustomPropTypes from '../../utils/PropTypes'; 6 | 7 | export default class Throttle extends Component { 8 | static propTypes = { 9 | parameters: ImmutablePropTypes.contains({ 10 | numberOfPanels: PropTypes.number.isRequired, 11 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | scaleEnabled: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | scaleType: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | }).isRequired, 17 | setPosition: PropTypes.func.isRequired, 18 | setScaleEnabled: PropTypes.func.isRequired, 19 | setScaleType: PropTypes.func.isRequired, 20 | setVisibleOn: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | _setScaleEnabled = (enabled) => { 28 | this.props.setScaleEnabled('throttle', enabled); 29 | } 30 | 31 | _setScaleType = (type) => { 32 | this.props.setScaleType('throttle', type); 33 | } 34 | 35 | render() { 36 | const { setPosition, setVisibleOn } = this.props; 37 | const { 38 | numberOfPanels, positionX, positionY, visibleOn, scaleEnabled, scaleType 39 | } = this.props.parameters; 40 | const scaleEnabledOptions = [ 41 | { value: 0, label: 'number' }, { value: 1, label: 'scale' }, 42 | ]; 43 | const scaleTypeOptions = [ 44 | { value: 0, label: 'vertical' }, { value: 1, label: 'horizontal' } 45 | ]; 46 | 47 | return ( 48 | 49 | 52 | 53 | 56 | 57 | 58 | 61 | 62 | 65 | 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/config/settings/index.js: -------------------------------------------------------------------------------- 1 | import AbsoluteAltitude from './AbsoluteAltitude'; 2 | import Alarms from './Alarms'; 3 | import AltitudeScale from './AltitudeScale'; 4 | import ArmState from './ArmState'; 5 | import ArtificialHorizon from './ArtificialHorizon'; 6 | import Attitude3d from './Attitude3d'; 7 | import BatteryConsumed from './BatteryConsumed'; 8 | import BatteryCurrent from './BatteryCurrent'; 9 | import BatteryRemaining from './BatteryRemaining'; 10 | import BatteryVoltage from './BatteryVoltage'; 11 | import ClimbRate from './ClimbRate'; 12 | import Compass from './Compass'; 13 | import Efficiency from './Efficiency'; 14 | import FlightMode from './FlightMode'; 15 | import HomeLatitude from './HomeLatitude'; 16 | import HomeLongitude from './HomeLongitude'; 17 | import Gps2Hdop from './Gps2Hdop'; 18 | import Gps2Latitude from './Gps2Latitude'; 19 | import Gps2Longitude from './Gps2Longitude'; 20 | import Gps2Status from './Gps2Status'; 21 | import GpsHdop from './GpsHdop'; 22 | import GpsLatitude from './GpsLatitude'; 23 | import GpsLongitude from './GpsLongitude'; 24 | import GpsStatus from './GpsStatus'; 25 | import HomeDirection from './HomeDirection'; 26 | import HomeDirectionDebugInfo from './HomeDirectionDebugInfo'; 27 | import HomeDistance from './HomeDistance'; 28 | import LinkQuality from './LinkQuality'; 29 | import RCChannels from './RCChannels'; 30 | import Map from './Map'; 31 | import Radar from './Radar'; 32 | import RelativeAltitude from './RelativeAltitude'; 33 | import Rssi from './Rssi'; 34 | import Serial from './Serial'; 35 | import SpeedAir from './SpeedAir'; 36 | import SpeedGround from './SpeedGround'; 37 | import SpeedScale from './SpeedScale'; 38 | import Switching from './Switching'; 39 | import Throttle from './Throttle'; 40 | import Time from './Time'; 41 | import TotalTrip from './TotalTrip'; 42 | import VarioGraph from './VarioGraph'; 43 | import Video from './Video'; 44 | import Watt from './Watt'; 45 | import Wind from './Wind'; 46 | import WPDistance from './WPDistance'; 47 | 48 | export default { 49 | AbsoluteAltitude, 50 | Alarms, 51 | AltitudeScale, 52 | ArmState, 53 | ArtificialHorizon, 54 | Attitude3d, 55 | BatteryConsumed, 56 | BatteryCurrent, 57 | BatteryRemaining, 58 | BatteryVoltage, 59 | ClimbRate, 60 | Compass, 61 | Efficiency, 62 | FlightMode, 63 | HomeLatitude, 64 | HomeLongitude, 65 | Gps2Hdop, 66 | Gps2Latitude, 67 | Gps2Longitude, 68 | Gps2Status, 69 | GpsHdop, 70 | GpsLatitude, 71 | GpsLongitude, 72 | GpsStatus, 73 | HomeDirection, 74 | HomeDirectionDebugInfo, 75 | HomeDistance, 76 | LinkQuality, 77 | RCChannels, 78 | Map, 79 | Radar, 80 | RelativeAltitude, 81 | Rssi, 82 | Serial, 83 | SpeedAir, 84 | SpeedScale, 85 | Switching, 86 | SpeedGround, 87 | Throttle, 88 | Time, 89 | TotalTrip, 90 | VarioGraph, 91 | Video, 92 | Watt, 93 | Wind, 94 | WPDistance, 95 | }; 96 | -------------------------------------------------------------------------------- /src/preview/Throttle.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | 6 | export default class ThrottlePreview extends PreviewBase { 7 | static propTypes = { 8 | panel: PropTypes.number.isRequired, 9 | positionX: PropTypes.number.isRequired, 10 | positionY: PropTypes.number.isRequired, 11 | scaleEnabled: PropTypes.number.isRequired, 12 | scaleType: PropTypes.number, 13 | throttle: PropTypes.number.isRequired, 14 | visibleOn: PropTypes.number.isRequired, 15 | } 16 | 17 | draw() { 18 | const font = fonts.getFont(0); 19 | this.canvas.clear(); 20 | 21 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 22 | if (this.props.scaleEnabled) { 23 | const string = `${this.props.throttle.toFixed(0)}%`; 24 | const position = Canvas.calculateStringPosition(string, 0, 0, 2, 2, font); 25 | const posX = 75; 26 | const posY = 25; 27 | this.canvas.drawString(string, posX - position.width, posY, font); 28 | const posThY = Math.round(this.props.throttle * 0.5, 0); 29 | const posThX = posX - 25 + posThY; 30 | 31 | if (this.props.scaleType === 0) { 32 | this.canvas.drawFilledRectangle(posX + 3, posY + 25 - posThY, 5, posThY); 33 | this.canvas.drawLine(posX + 3, posY - 25, posX + 7, posY - 25); 34 | this.canvas.drawLine(posX + 3, posY + 25 - posThY, posX + 7, posY + 25 - posThY); 35 | this.canvas.drawLine(posX + 3, posY - 25, posX + 3, posY + 25 - posThY); 36 | this.canvas.drawLine(posX + 7, posY - 25, posX + 7, posY + 25 - posThY); 37 | } else { 38 | this.canvas.drawFilledRectangle(posX - 25, posY + 10, posThY, 5); 39 | this.canvas.drawLine(posThX, posY + 10, posX + 25, posY + 10); 40 | this.canvas.drawLine(posThX, posY + 14, posX + 25, posY + 14); 41 | this.canvas.drawLine(posX + 25, posY + 10, posX + 25, posY + 14); 42 | this.canvas.drawLine(posX - 25, posY + 10, posX - 25, posY + 14); 43 | } 44 | } else { 45 | const string = `thr ${this.props.throttle.toFixed(0)}%`; 46 | const position = Canvas.calculateStringPosition(string, 0, 0, 2, 2, font); 47 | const posX = 75; 48 | const posY = 25; 49 | this.canvas.drawString(string, posX - position.width, posY, font); 50 | } 51 | } 52 | } 53 | 54 | render() { 55 | const { positionX, positionY } = this.props; 56 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 57 | 58 | return ( 59 | !visible ? 60 | : 61 | 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/preview/Radar.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Canvas from '../utils/Canvas'; 3 | import PreviewBase from './PreviewBase'; 4 | import fonts from '../utils/fonts'; 5 | 6 | export default class Radar extends PreviewBase { 7 | static propTypes = { 8 | heading: PropTypes.number.isRequired, 9 | homeBearing: PropTypes.number.isRequired, 10 | homeRadius: PropTypes.number.isRequired, 11 | panel: PropTypes.number.isRequired, 12 | positionX: PropTypes.number.isRequired, 13 | positionY: PropTypes.number.isRequired, 14 | radius: PropTypes.number.isRequired, 15 | visibleOn: PropTypes.number.isRequired, 16 | wpBearing: PropTypes.number.isRequired, 17 | wpNumber: PropTypes.number.isRequired, 18 | wpRadius: PropTypes.number.isRequired, 19 | } 20 | 21 | draw() { 22 | this.canvas.clear(); 23 | 24 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 25 | const width = this.refs.canvas.width; 26 | const height = this.refs.canvas.height; 27 | const { homeRadius, radius, wpRadius } = this.props; 28 | 29 | const posX = width / 2; 30 | const posY = height / 2; 31 | this.canvas.drawCircle(posX, posY, radius, true); 32 | this.canvas.save(); 33 | this.canvas.translate(width / 2, height / 2); 34 | this.canvas.rotate(this.props.heading * Math.PI / 180); 35 | this.canvas.translate(- width / 2, - height / 2); 36 | this.canvas.drawLine(posX, posY - 7, posX - 3, posY + 7, true); 37 | this.canvas.drawLine(posX, posY - 7, posX + 3, posY + 7, true); 38 | this.canvas.restore(); 39 | 40 | const font = fonts.getFont(0); 41 | const homeString = 'H'; 42 | const homeHeading = this.props.homeBearing; 43 | const homeX = posX + homeRadius * Math.sin(homeHeading * Math.PI / 180); 44 | const homeY = posY - homeRadius * Math.cos(homeHeading * Math.PI / 180); 45 | const homePosition = Canvas.calculateStringPosition( 46 | homeString, homeX, homeY, 1, 1, font); 47 | this.canvas.drawString(homeString, homePosition.left, homePosition.top, font); 48 | 49 | const wpString = this.props.wpNumber.toFixed(0); 50 | const wpHeading = this.props.wpBearing; 51 | const wpX = posX + wpRadius * Math.sin(wpHeading * Math.PI / 180); 52 | const wpY = posY - wpRadius * Math.cos(wpHeading * Math.PI / 180); 53 | const wpPosition = Canvas.calculateStringPosition(wpString, wpX, wpY, 1, 1, font); 54 | this.canvas.drawString(wpString, wpPosition.left, wpPosition.top, font); 55 | } 56 | } 57 | 58 | render() { 59 | const { positionX, positionY } = this.props; 60 | const radius = this.props.radius; 61 | const width = radius * 2 + 20; 62 | const height = radius * 2 + 20; 63 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 64 | 65 | return ( 66 | !visible ? 67 | : 68 | 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app.global.css: -------------------------------------------------------------------------------- 1 | label, span, div, select, option, input[readonly] { 2 | -webkit-user-select: none; 3 | } 4 | 5 | html { 6 | font-size: 10px; 7 | } 8 | 9 | #root { 10 | height: 100%; 11 | } 12 | 13 | .column { 14 | display: inline-block; 15 | vertical-align: top; 16 | } 17 | 18 | .main { 19 | position: absolute; 20 | left: 0; 21 | top: 64px; 22 | right: 0; 23 | bottom: 0; 24 | } 25 | 26 | .parameters { 27 | position: absolute; 28 | right: 0px; 29 | padding: 5px; 30 | left: 400px; 31 | top: 0; 32 | display:block; 33 | bottom: 0; 34 | overflow-y: auto; 35 | } 36 | 37 | [data-react-toolbox= "dropdown"] label { 38 | white-space: nowrap; 39 | } 40 | 41 | .sidebar { 42 | padding: 5px; 43 | position: absolute; 44 | width: 400px; 45 | top: 5px; 46 | left: 5px; 47 | bottom: 0; 48 | overflow-y: auto; 49 | } 50 | 51 | .sidebar .panel label { 52 | display: inline-block; 53 | margin-right: 0.5rem; 54 | } 55 | 56 | .sidebar .connection { 57 | overflow: visible; 58 | margin: 5px 0; 59 | } 60 | 61 | .sidebar button { 62 | margin-right: 0.5rem; 63 | } 64 | 65 | .preview-card { 66 | overflow: visible; 67 | margin: 5px 0; 68 | } 69 | 70 | .preview { 71 | position: relative; 72 | overflow: hidden; 73 | } 74 | 75 | .preview img { 76 | width: 100%; 77 | height: 100%; 78 | } 79 | 80 | .preview .preview-widget { 81 | position: absolute; 82 | overflow: hidden; 83 | /*cursor: move;*/ 84 | } 85 | 86 | /*.preview .preview-widget:hover { 87 | box-shadow: inset 0 0 5px white, 0 0 5px black; 88 | }*/ 89 | 90 | .parameters .parameter-list { 91 | overflow: visible; 92 | padding: 5px; 93 | display: inline-block; 94 | width: 100%; 95 | vertical-align: top; 96 | } 97 | 98 | .parameters .parameter-list > div { 99 | overflow: visible; 100 | } 101 | 102 | @media(min-width: 1090px) { 103 | .parameters .parameter-list { 104 | width: 50%; 105 | } 106 | } 107 | 108 | @media(min-width: 1390px) { 109 | .parameters .parameter-list { 110 | width: 33%; 111 | } 112 | } 113 | 114 | @media(min-width: 1690px) { 115 | .parameters .parameter-list { 116 | width: 25%; 117 | } 118 | } 119 | 120 | @media(min-width: 1990px) { 121 | .parameters .parameter-list { 122 | width: 20%; 123 | } 124 | } 125 | 126 | .modified { 127 | background-color: #EEEEEE; 128 | } 129 | 130 | .label { 131 | line-height: 1.6rem; 132 | color: rgba(0, 0, 0, 0.26); 133 | font-size: 1.2rem; 134 | margin-bottom: 0.6rem; 135 | display: inline-block; 136 | } 137 | 138 | .material-icons { 139 | font-family: 'Material Icons'; 140 | font-weight: normal; 141 | font-style: normal; 142 | font-size: 24px; 143 | /* Preferred icon size */ 144 | display: inline-block; 145 | line-height: 1; 146 | text-transform: none; 147 | letter-spacing: normal; 148 | word-wrap: normal; 149 | /* Support for all WebKit browsers. */ 150 | -webkit-font-smoothing: antialiased; 151 | /* Support for Safari and Chrome. */ 152 | text-rendering: optimizeLegibility; 153 | /* Support for Firefox. */ 154 | -moz-osx-font-smoothing: grayscale; 155 | /* Support for IE. */ 156 | font-feature-settings: 'liga'; } 157 | 158 | [data-react-toolbox="input"] { 159 | padding: 2rem 1rem; 160 | } 161 | 162 | [data-react-toolbox="input"] label { 163 | left: 1rem; 164 | } 165 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/xcshareddata/xcschemes/scheme.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/xcshareddata/xcschemes/scheme copy.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/config/settings/LinkQuality.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Input from '../../components/Input'; 3 | import Column from '../../components/Column'; 4 | import Select from '../parameters/Select'; 5 | import SimpleSettings from './SimpleSettings'; 6 | import ImmutablePropTypes from 'react-immutable-proptypes'; 7 | import CustomPropTypes from '../../utils/PropTypes'; 8 | 9 | export default class LinkQuality extends Component { 10 | static propTypes = { 11 | parameters: ImmutablePropTypes.contains({ 12 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | max: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | min: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | numberOfPanels: PropTypes.number.isRequired, 17 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 18 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 19 | raw: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 20 | type: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 21 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 22 | }).isRequired, 23 | setFontSize: PropTypes.func.isRequired, 24 | setHAlignment: PropTypes.func.isRequired, 25 | setMax: PropTypes.func.isRequired, 26 | setMin: PropTypes.func.isRequired, 27 | setPosition: PropTypes.func.isRequired, 28 | setRaw: PropTypes.func.isRequired, 29 | setType: PropTypes.func.isRequired, 30 | setVisibleOn: PropTypes.func.isRequired, 31 | } 32 | 33 | shouldComponentUpdate(nextProps) { 34 | return !this.props.parameters.equals(nextProps.parameters); 35 | } 36 | 37 | _setMin = (min) => { 38 | this.props.setMin('linkQuality', parseInt(min, 10)); 39 | } 40 | 41 | _setMax = (max) => { 42 | this.props.setMax('linkQuality', parseInt(max, 10)); 43 | } 44 | 45 | _setType = (type) => { 46 | this.props.setType('linkQuality', type); 47 | } 48 | 49 | _setRaw = (raw) => { 50 | this.props.setRaw('linkQuality', raw); 51 | } 52 | 53 | render() { 54 | const { max, min, raw, type } = this.props.parameters; 55 | const rawOptions = [ 56 | { value: 0, label: 'percentage' }, { value: 1, label: 'raw value' } 57 | ]; 58 | const typeOptions = [ 59 | { value: 5, label: 'rc 5' }, { value: 6, label: 'rc 6' }, 60 | { value: 7, label: 'rc 7' }, { value: 8, label: 'rc 8' }, 61 | { value: 9, label: 'rc 9' }, { value: 10, label: 'rc 10' }, 62 | { value: 11, label: 'rc 11' }, { value: 12, label: 'rc 12' }, 63 | { value: 13, label: 'rc 13' }, { value: 14, label: 'rc 14' }, 64 | { value: 15, label: 'rc 15' }, { value: 16, label: 'rc 16' } 65 | ]; 66 | const minInput = 1000; 67 | const maxInput = 2000; 68 | 69 | return ( 70 | 71 | 72 | 75 | 76 | 77 | 80 | 81 | 82 | 86 | 87 | 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/config/settings/Rssi.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Input from '../../components/Input'; 3 | import Column from '../../components/Column'; 4 | import Select from '../parameters/Select'; 5 | import SimpleSettings from './SimpleSettings'; 6 | import ImmutablePropTypes from 'react-immutable-proptypes'; 7 | import CustomPropTypes from '../../utils/PropTypes'; 8 | 9 | export default class Rssi extends Component { 10 | static propTypes = { 11 | parameters: ImmutablePropTypes.contains({ 12 | fontSize: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | hAlignment: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | max: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | min: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | numberOfPanels: PropTypes.number.isRequired, 17 | positionX: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 18 | positionY: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 19 | raw: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 20 | type: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 21 | visibleOn: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 22 | }).isRequired, 23 | setFontSize: PropTypes.func.isRequired, 24 | setHAlignment: PropTypes.func.isRequired, 25 | setMax: PropTypes.func.isRequired, 26 | setMin: PropTypes.func.isRequired, 27 | setPosition: PropTypes.func.isRequired, 28 | setRaw: PropTypes.func.isRequired, 29 | setType: PropTypes.func.isRequired, 30 | setVisibleOn: PropTypes.func.isRequired, 31 | } 32 | 33 | shouldComponentUpdate(nextProps) { 34 | return !this.props.parameters.equals(nextProps.parameters); 35 | } 36 | 37 | _setMin = (min) => { 38 | this.props.setMin('rssi', parseInt(min, 10)); 39 | } 40 | 41 | _setMax = (max) => { 42 | this.props.setMax('rssi', parseInt(max, 10)); 43 | } 44 | 45 | _setType = (type) => { 46 | this.props.setType('rssi', type); 47 | } 48 | 49 | _setRaw = (raw) => { 50 | this.props.setRaw('rssi', raw); 51 | } 52 | 53 | render() { 54 | const { max, min, raw, type } = this.props.parameters; 55 | const rawOptions = [ 56 | { value: 0, label: 'percentage' }, { value: 1, label: 'raw value' } 57 | ]; 58 | const typeOptions = [ 59 | { value: 0, label: 'mavlink' }, { value: 5, label: 'rc 5' }, 60 | { value: 6, label: 'rc 6' }, { value: 7, label: 'rc 7' }, 61 | { value: 8, label: 'rc 8' }, { value: 9, label: 'rc 9' }, 62 | { value: 10, label: 'rc 10' }, { value: 11, label: 'rc 11' }, 63 | { value: 12, label: 'rc 12' }, { value: 13, label: 'rc 13' }, 64 | { value: 14, label: 'rc 14' }, { value: 15, label: 'rc 15' }, 65 | { value: 16, label: 'rc 16' } 66 | ]; 67 | const minInput = type === 0 ? 0 : 1000; 68 | const maxInput = type === 0 ? 255 : 2000; 69 | 70 | return ( 71 | 72 | 73 | 76 | 77 | 78 | 81 | 82 | 83 | 87 | 88 | 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | // const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 6 | const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); 7 | const NoErrorsPlugin = require('webpack/lib/NoErrorsPlugin'); 8 | const OccurenceOrderPlugin = require('webpack/lib/optimize/OccurenceOrderPlugin'); 9 | const path = require('path'); 10 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); 11 | const WebpackNotifierPlugin = require('webpack-notifier'); 12 | const fs = require('fs'); 13 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, 'app', 'manifest.json'))); 14 | 15 | const production = Boolean(process.env.PRODUCTION); 16 | 17 | const plugins = production ? 18 | [ 19 | new OccurenceOrderPlugin(), 20 | new DefinePlugin({ 21 | __DEV__: false, 22 | 'process.env': { 23 | NODE_ENV: JSON.stringify('production') 24 | }, 25 | VERSION: JSON.stringify(manifest.version), 26 | }), 27 | new UglifyJsPlugin({ 28 | compressor: { 29 | screw_ie8: true, 30 | warnings: false 31 | } 32 | }), 33 | // new ExtractTextPlugin('style.css', { allChunks: true }) 34 | ] : 35 | [ 36 | new HotModuleReplacementPlugin(), 37 | new NoErrorsPlugin(), 38 | new WebpackNotifierPlugin(), 39 | new DefinePlugin({ 40 | __DEV__: true, 41 | 'process.env': { 42 | NODE_ENV: JSON.stringify('development') 43 | }, 44 | VERSION: JSON.stringify(manifest.version), 45 | }) 46 | ]; 47 | 48 | module.exports = { 49 | debug: !production, 50 | devTool: production ? 'source-map' : 'cheap-module-eval-source-map', 51 | entry: { 52 | index: './src/index', 53 | eventPage: './src/eventPage', 54 | }, 55 | module: { 56 | loaders: [{ 57 | test: /\.jsx?$/, 58 | loaders: ['babel-loader'], 59 | exclude: /node_modules/ 60 | }, { 61 | test: /\.json$/, 62 | loader: 'json-loader' 63 | }, { 64 | test: /\.scss$/, 65 | loaders: ['style', 'css?modules', 'sass'], 66 | include: path.resolve(__dirname, './node_modules'), 67 | }, { 68 | test: /\.css$/, 69 | loaders: ['style', 'css?modules'], 70 | include: path.resolve(__dirname, './node_modules'), 71 | }, { 72 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 73 | loader: 'url-loader?limit=10000&mimetype=application/font-woff' 74 | }, { 75 | test: /\.png$/, 76 | loader: 'url-loader?limit=10000&mimetype=application/font-woff' 77 | }, { 78 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 79 | loader: 'file-loader' 80 | }, { 81 | test: /\.global\.css$/, 82 | loaders: ['style-loader', 'css-loader'], 83 | exclude: path.resolve(__dirname, './node_modules'), 84 | }, { 85 | test: /^((?!\.global).)*\.css$/, 86 | loaders: [ 87 | // ExtractTextPlugin.extract( 88 | 'style-loader', 89 | production ? 90 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' : 91 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 92 | ], 93 | exclude: path.resolve(__dirname, './node_modules'), 94 | // ) 95 | } 96 | ] 97 | }, 98 | output: { 99 | path: path.join(__dirname, 'app', 'dist'), 100 | filename: '[name].js', 101 | libraryTarget: 'var', 102 | publicPath: './dist/', 103 | }, 104 | resolve: { 105 | extensions: ['', '.js', '.jsx', '.scss'], 106 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] 107 | }, 108 | node: { 109 | child_process: 'empty', 110 | fs: 'empty', 111 | net: 'empty', 112 | }, 113 | plugins, 114 | externals: [ 115 | ] 116 | }; 117 | -------------------------------------------------------------------------------- /src/preview/RCChannels.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import * as Canvas_types from '../utils/Canvas'; 3 | import Canvas from '../utils/Canvas'; 4 | import PreviewBase from './PreviewBase'; 5 | import fonts from '../utils/fonts'; 6 | 7 | export default class RCChannelsPreview extends PreviewBase { 8 | static propTypes = { 9 | panel: PropTypes.number.isRequired, 10 | positionX: PropTypes.number.isRequired, 11 | positionY: PropTypes.number.isRequired, 12 | visibleOn: PropTypes.number.isRequired, 13 | } 14 | 15 | draw() { 16 | const font = fonts.getFont(0); 17 | this.canvas.clear(); 18 | 19 | if ((this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0) { 20 | var default_channel_count = 8; 21 | 22 | // Just some sample channel values, possibly not realistic 23 | var channelValues = [ 1524, 1520, 1750, 1250, 1400, 1800, 1950, 1515]; 24 | 25 | var bar_x_offset = 85; 26 | var currentLineYOffset = 0; 27 | // TODO: Could make bar width configurable in firmware, but we'll see if anyone cares first 28 | var bar_width = 40; 29 | for (let chanNumber = 1; chanNumber <= default_channel_count; chanNumber++) { 30 | var currentChannelPwmValue = channelValues[chanNumber-1]; 31 | // Draw the channel label and numeric value ('CH 2 1250') 32 | var curChanString = 'CH ' + chanNumber + ' ' + currentChannelPwmValue + ' '; 33 | var chanStringPosition = Canvas.calculateStringPosition(curChanString, 0, currentLineYOffset, Canvas_types.H_ALIGNMENT_LEFT, Canvas_types.V_ALIGNMENT_TOP, font); 34 | this.canvas.drawString(curChanString, 0, currentLineYOffset, font); 35 | 36 | // Draw the bar to the side of the text that represents the current numeric value 37 | var barXPos = bar_x_offset; 38 | var barYPos = currentLineYOffset; 39 | var barHeight = chanStringPosition.height; 40 | var isBlack = false; 41 | var isOutline = true; 42 | this.canvas.drawRectangle(barXPos, barYPos, bar_width, chanStringPosition.height, isBlack, isOutline); 43 | 44 | // Normalize 1000-2000 PPM value to 0-1000 45 | var normalized_channel_value = currentChannelPwmValue - 1000; 46 | if (normalized_channel_value < 0) { 47 | normalized_channel_value = 0; 48 | } else if (normalized_channel_value > 1000) { 49 | normalized_channel_value = 1000; 50 | } 51 | 52 | // X offset position for the stripe relative to the bar 53 | var stripe_offset_x = (normalized_channel_value * bar_width) / 1000; 54 | 55 | // Draw vertical stripe marking channel value on bar rectangle 56 | var stripeXPos = barXPos + stripe_offset_x; 57 | this.canvas.drawLine(stripeXPos - 1, barYPos, stripeXPos - 1, barYPos + chanStringPosition.height, false, true); 58 | this.canvas.drawLine(stripeXPos, barYPos, stripeXPos, barYPos + chanStringPosition.height, false, true); 59 | this.canvas.drawLine(stripeXPos + 1, barYPos, stripeXPos + 1, barYPos + chanStringPosition.height, false, true); 60 | 61 | // Move Y position down one line 62 | currentLineYOffset += chanStringPosition.height; 63 | } 64 | } 65 | } 66 | 67 | render() { 68 | const { positionX, positionY } = this.props; 69 | const visible = (this.props.visibleOn & Math.pow(2, this.props.panel)) !== 0; 70 | 71 | return ( 72 | !visible ? 73 | : 74 | 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/config/settings/Switching.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ImmutablePropTypes from 'react-immutable-proptypes'; 3 | import Parameters from '../parameters'; 4 | import Column from '../../components/Column'; 5 | import Input from '../../components/Input'; 6 | import CustomPropTypes from '../../utils/PropTypes'; 7 | 8 | export default class Switching extends Component { 9 | static propTypes = { 10 | parameters: ImmutablePropTypes.contains({ 11 | panelChannel: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 12 | panelMode: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 13 | panelValue: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 14 | videoChannel: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 15 | videoMode: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 16 | videoValue: CustomPropTypes.value(PropTypes.number.isRequired).isRequired, 17 | }).isRequired, 18 | setChannel: PropTypes.func.isRequired, 19 | setMode: PropTypes.func.isRequired, 20 | setValue: PropTypes.func.isRequired, 21 | } 22 | 23 | shouldComponentUpdate(nextProps) { 24 | return !this.props.parameters.equals(nextProps.parameters); 25 | } 26 | 27 | _setChannel(key, value) { 28 | this.props.setChannel('switching', key, value); 29 | } 30 | 31 | _setValue(key, value) { 32 | this.props.setValue('switching', key, parseInt(value, 10)); 33 | } 34 | 35 | render() { 36 | const { 37 | panelChannel, panelMode, panelValue, videoChannel, videoMode, videoValue 38 | } = this.props.parameters; 39 | const channelOptions = [ 40 | { value: 0, label: 'disabled' }, { value: 1, label: 'rc 1' }, 41 | { value: 2, label: 'rc 2' }, { value: 3, label: 'rc 3' }, 42 | { value: 4, label: 'rc 4' }, { value: 5, label: 'rc 5' }, 43 | { value: 6, label: 'rc 6' }, { value: 7, label: 'rc 7' }, 44 | { value: 8, label: 'rc 8' }, { value: 9, label: 'rc 9' }, 45 | { value: 10, label: 'rc 10' }, { value: 11, label: 'rc 11' }, 46 | { value: 12, label: 'rc 12' }, { value: 13, label: 'rc 13' }, 47 | { value: 14, label: 'rc 14' }, { value: 15, label: 'rc 15' }, 48 | { value: 16, label: 'rc 16' } 49 | ]; 50 | 51 | const modeOptions = [ 52 | { value: 0, label: 'cycle' }, { value: 1, label: 'switch' } 53 | ]; 54 | 55 | return ( 56 | 57 | 58 | 61 | 62 | 63 | 66 | 67 | 68 | 71 | 72 | 73 | 76 | 77 | 78 | { panelMode.get('value') === 0 ? 79 | 82 | : null } 83 | 84 | 85 | { videoMode.get('value') === 0 ? 86 | 89 | : null } 90 | 91 | 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/config/reducer.js: -------------------------------------------------------------------------------- 1 | import eeprom from '../utils/eeprom'; 2 | 3 | import { 4 | ALARM_ENABLED, ALARM_VALUE, AS_BASE_STATE, BAUD_RATE, CHANNEL, 5 | FC_TYPE, FONT_SIZE, H_ALIGNMENT, MAX, MAX_PANELS, MIN, OFFSET, 6 | PARAMS_FROM_EEPROM, POSITION, RADIUS, RAW, SCALE, SCALE_ALIGNMENT, 7 | SCALE_ENABLED, SCALE_TYPE, TYPE, UNITS, V_ALIGNMENT, VALUE, 8 | MODE, VISIBLE_ON, 9 | } from './actions'; 10 | 11 | function setAsBaseState(state, baseState = null) { 12 | return state.updateIn(['originalState'], () => baseState || state); 13 | } 14 | 15 | const initialState = setAsBaseState(eeprom.toParameters(eeprom.defaultEEPROM)); 16 | 17 | export default function parameters(state = initialState, action) { 18 | const parameterName = action.parameter; 19 | const payload = action.payload; 20 | 21 | switch (action.type) { 22 | case ALARM_ENABLED: 23 | return state.setIn([parameterName, `${payload.alarm}Enabled`], payload.enabled); 24 | case ALARM_VALUE: 25 | return state.setIn([parameterName, `${payload.alarm}Value`], payload.value); 26 | case AS_BASE_STATE: 27 | return setAsBaseState(state, action.state); 28 | case BAUD_RATE: 29 | return state.setIn([parameterName, 'baudRate'], payload); 30 | case CHANNEL: 31 | return state.setIn([parameterName, `${payload.key}Channel`], payload.channel); 32 | case FC_TYPE: 33 | return state.updateIn([parameterName, 'fcType'], () => action.fcType); 34 | case FONT_SIZE: 35 | return state.setIn([parameterName, 'fontSize'], payload); 36 | case H_ALIGNMENT: 37 | return state.setIn([parameterName, 'hAlignment'], payload); 38 | case MAX: 39 | return state.setIn([parameterName, 'max'], payload); 40 | case MAX_PANELS: 41 | return state.setIn([parameterName, 'maxPanels'], payload); 42 | case MIN: 43 | return state.setIn([parameterName, 'min'], payload); 44 | case OFFSET: 45 | return state 46 | .setIn([parameterName, 'offsetX'], payload.x) 47 | .setIn([parameterName, 'offsetY'], payload.y); 48 | case PARAMS_FROM_EEPROM: { 49 | let eepromData = action.eepromData; 50 | for (let i = eepromData.length; i > 0; i--) { 51 | if (eepromData[i - 1] !== 0) { 52 | eepromData = eepromData.slice(0, i); 53 | break; 54 | } 55 | } 56 | 57 | const defaultEEPROM = eeprom.defaultEEPROM; 58 | if (eepromData.length < defaultEEPROM.length) { 59 | const missingData = defaultEEPROM.slice(eepromData.length, defaultEEPROM.length); 60 | eepromData = eepromData.concat(missingData); 61 | } 62 | return setAsBaseState(eeprom.toParameters(eepromData)); 63 | } 64 | case POSITION: 65 | return state 66 | .setIn([parameterName, 'positionX'], payload.x) 67 | .setIn([parameterName, 'positionY'], payload.y); 68 | case RADIUS: 69 | return state.updateIn([parameterName, action.key], () => action.radius); 70 | case RAW: 71 | return state.updateIn([parameterName, 'raw'], () => action.raw); 72 | case SCALE: 73 | return state.updateIn([parameterName, 'scale'], () => action.scale); 74 | case SCALE_ALIGNMENT: 75 | return state.updateIn([parameterName, 'scaleAlignment'], () => action.scaleAlignment); 76 | case SCALE_ENABLED: 77 | return state.updateIn([parameterName, 'scaleEnabled'], () => action.scaleEnabled); 78 | case SCALE_TYPE: 79 | return state.updateIn([parameterName, 'scaleType'], () => action.scaleType); 80 | case TYPE: 81 | return state.updateIn([parameterName, 'type'], () => action.typeValue); 82 | case UNITS: 83 | return state.updateIn([parameterName, 'units'], () => action.units); 84 | case V_ALIGNMENT: 85 | return state.updateIn([parameterName, 'vAlignment'], () => action.vAlignment); 86 | case VALUE: 87 | return state.updateIn([parameterName, `${action.key}Value`], () => action.value); 88 | case MODE: 89 | return state.updateIn([parameterName, `${action.prefix}Mode`], () => action.mode); 90 | case VISIBLE_ON: 91 | return state.setIn([parameterName, 'visibleOn'], payload); 92 | default: 93 | return state; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/pixler/reducer.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Immutable from 'immutable'; 3 | import pixler from '../../src/pixler/reducer'; 4 | import { 5 | EMPTY, SHAPE, OUTLINE, CLEAR, MIRROR, SET_FONT_SIZE, SET_OUTLINE, SET_SHAPE, SET_PIXEL, 6 | } from '../../src/pixler/actions'; 7 | 8 | 9 | describe('reducers', () => { 10 | describe('pixler', () => { 11 | it('should handle initial state', () => { 12 | const state = pixler(undefined, {}); 13 | expect(state).keys('fontSize', 'outline', 'shape'); 14 | expect(state.get('outline')).to.equal( 15 | Immutable.List.of(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 16 | expect(state.get('shape')).to.equal(Immutable.List.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 17 | }); 18 | 19 | it('should handle CLEAR', () => { 20 | const state = Immutable.fromJS({ 21 | outline: [1, 1, 1, 1, 1, 1, 1, 1], 22 | shape: [1, 1, 1, 1, 1, 1, 1, 1], 23 | fontSize: 0, 24 | }); 25 | const newState = pixler(state, { type: CLEAR }); 26 | const zeroList = Immutable.List.of(0, 0, 0, 0, 0, 0, 0, 0); 27 | const ffList = Immutable.List.of(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); 28 | 29 | expect(newState.get('outline')).to.equal(ffList); 30 | expect(newState.get('shape')).to.equal(zeroList); 31 | }); 32 | 33 | it('should handle MIRROR', () => { 34 | const state = Immutable.fromJS({ 35 | outline: [0x80, 0x8, 0, 0, 0, 0, 0, 0], 36 | shape: [0x81, 0x38, 0, 0, 0, 0, 0, 0], 37 | fontSize: 0, 38 | }); 39 | const newState = pixler(state, { type: MIRROR }); 40 | 41 | expect(newState.get('outline')).to.equal(Immutable.List.of(0x1, 0x10, 0, 0, 0, 0, 0, 0)); 42 | expect(newState.get('shape')).to.equal(Immutable.List.of(0x81, 0x1c, 0, 0, 0, 0, 0, 0)); 43 | }); 44 | 45 | it('should handle SET_FONT_SIZE', () => { 46 | const state = pixler(undefined, { type: SET_FONT_SIZE, payload: 1 }); 47 | 48 | expect(state.get('fontSize')).to.equal(1); 49 | expect(state.get('outline')).to.have.size(14); 50 | expect(state.get('shape')).to.have.size(14); 51 | 52 | const newState = pixler(state, { type: SET_FONT_SIZE, payload: 0 }); 53 | 54 | expect(newState.get('fontSize')).to.equal(0); 55 | expect(newState.get('outline')).to.have.size(10); 56 | expect(newState.get('shape')).to.have.size(10); 57 | 58 | const lastState = pixler(newState, { type: SET_FONT_SIZE, payload: 2 }); 59 | 60 | expect(lastState.get('fontSize')).to.equal(2); 61 | expect(lastState.get('outline')).to.have.size(18); 62 | expect(lastState.get('shape')).to.have.size(18); 63 | }); 64 | 65 | it('should handle SET_OUTLINE', () => { 66 | const state = pixler(undefined, { 67 | type: SET_OUTLINE, payload: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff] 68 | }); 69 | expect(state.get('outline')).to.equal( 70 | Immutable.List.of(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 71 | }); 72 | 73 | it('should handle SET_SHAPE', () => { 74 | const state = pixler(undefined, { 75 | type: SET_SHAPE, payload: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 76 | }); 77 | expect(state.get('shape')).to.equal( 78 | Immutable.List.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 79 | }); 80 | 81 | it('should handle SET_PIXEL', () => { 82 | const state = pixler(undefined, { type: SET_PIXEL, pixelType: SHAPE, row: 0, column: 0 }); 83 | it('for SHAPE', () => { 84 | expect(state.get('shape')).to.equal(Immutable.List.of(0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 85 | expect(state.get('outline')).to.equal( 86 | Immutable.List.of(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 87 | }); 88 | 89 | const newState = pixler(state, { type: SET_PIXEL, pixelType: OUTLINE, row: 0, column: 1 }); 90 | it('for OUTLINE', () => { 91 | expect(newState.get('shape')).to.equal(Immutable.List.of(0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 92 | expect(newState.get('outline')).to.equal( 93 | Immutable.List.of(0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 94 | }); 95 | 96 | const lastState = pixler(newState, { type: SET_PIXEL, pixelType: EMPTY, row: 0, column: 0 }); 97 | it('for EMPTY', () => { 98 | expect(lastState.get('shape')).to.equal(Immutable.List.of(0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 99 | expect(lastState.get('outline')).to.equal( 100 | Immutable.List.of(0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 101 | }); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /etc/dummy.xcodeproj/xcshareddata/xcschemes/blablabla.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | --------------------------------------------------------------------------------