diff --git a/.gitignore b/.gitignore index 4e2e516..92a5879 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist/ builds/ packaged dist -.env \ No newline at end of file +.env +.vscode \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..215db93 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "semi": false, + "singleQuote": true + } \ No newline at end of file diff --git a/main.js b/main.js index 1646359..f006c84 100644 --- a/main.js +++ b/main.js @@ -1,56 +1,53 @@ -'use strict'; -const { autoUpdater } = require("electron-updater"); -const log = require('electron-log'); -const { app, BrowserWindow, systemPreferences, Tray, ipcMain, shell, dialog} = require('electron'); +'use strict' +const { autoUpdater } = require('electron-updater') +const log = require('electron-log') +const { + app, + BrowserWindow, + systemPreferences, + Tray, + ipcMain, + shell, + dialog +} = require('electron') const path = require('path') const url = require('url') const Positioner = require('electron-positioner') const tesla = require('./tesla-api') -const Store = require('electron-store'); -const store = new Store(); -const Poller = require('./poller'); -const contextMenu = require('electron-context-menu'); +const Store = require('electron-store') +const store = new Store() +const Poller = require('./poller') +const contextMenu = require('electron-context-menu') +const isDev = require('electron-is-dev') // Logging -autoUpdater.logger = log; -autoUpdater.logger.transports.file.level = 'info'; -log.info('Nikola App starting...'); +autoUpdater.logger = log +autoUpdater.logger.transports.file.level = 'info' +log.info('Nikola App starting...') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow; - -// Keep a reference for dev mode -let dev = false; -if (process.defaultApp || /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || /[\\/]electron[\\/]/.test(process.execPath)) { - dev = true; -} +// Keep a global reference of the window object +let mainWindow function createWindow() { - - // Create the browser window. mainWindow = new BrowserWindow({ width: 300, height: 450, show: true, frame: false, - title: "Nikola", + title: 'Nikola', fullscreenable: false, resizable: false, transparent: true, titleBarStyle: 'customButtonsOnHover', webPreferences: { - // Prevents renderer process code from not running when window is - // hidden backgroundThrottling: false, - nodeIntegration: true, + nodeIntegration: true } }) // tray stuff - let tray; + let tray if (process.platform === 'darwin') { if (systemPreferences.isDarkMode()) { @@ -62,22 +59,22 @@ function createWindow() { } if (process.platform !== 'darwin') { - tray = new Tray(path.join(__dirname, 'src', 'assets', 'img', 'win_tray.png')) + tray = new Tray( + path.join(__dirname, 'src', 'assets', 'img', 'win_tray.png') + ) } - - // Don't show the app in the dock + // Don't show the app in the dock (OSX) if (process.platform === 'darwin') { app.dock.hide() // Main window behavior - mainWindow.on('blur', () => { - mainWindow.hide() - }) + mainWindow.on('blur', () => { + mainWindow.hide() + }) } - // Show detached devtools (for development) - if (dev && process.argv.indexOf('--noDevServer') === -1) { + if (isDev) { mainWindow.openDevTools({ mode: 'detach' }) @@ -87,30 +84,38 @@ function createWindow() { let bounds = tray.getBounds() mainWindow.webContents.on('did-finish-load', () => { - - if(store.get('betaReleases')){ - autoUpdater.channel = "beta" + // Auto update features + if (!isDev) { + store.get('betaReleases') + ? (autoUpdater.channel = 'beta') + : (autoUpdater.channel = 'latest') + autoUpdater.checkForUpdates() } - autoUpdater.checkForUpdates(); - setInterval(() => { - if(store.get('betaReleases')){ - autoUpdater.channel = "beta" + if (!isDev) { + store.get('betaReleases') + ? (autoUpdater.channel = 'beta') + : (autoUpdater.channel = 'latest') + autoUpdater.checkForUpdates() } - autoUpdater.checkForUpdates() - }, 300000); - - const dialogOptions = {type: 'info', buttons: ['Restart and install', 'Not now'], message: 'A new Nikola version has been downloaded!'} + }, 300000) - autoUpdater.on('update-downloaded', (info) => { - dialog.showMessageBox(dialogOptions, i => i === 0 ? autoUpdater.quitAndInstall() : null) - }) + const dialogOptions = { + type: 'info', + buttons: ['Restart and install', 'Not now'], + message: 'A new Nikola version has been downloaded!' + } - autoUpdater.on('error', (err) => { - log.error(err) - }) + autoUpdater.on('update-downloaded', info => { + dialog.showMessageBox(dialogOptions, i => + i === 0 ? autoUpdater.quitAndInstall() : null + ) + }) + autoUpdater.on('error', err => { + log.error(err) + }) if (process.platform === 'darwin') { bounds = tray.getBounds() @@ -137,13 +142,13 @@ function createWindow() { } // get tesla Data - let authToken; + let authToken const getTeslaData = async () => { authToken = store.get('authToken') if (mainWindow.isVisible() && authToken) { try { - let vehicle; + let vehicle try { vehicle = await tesla.vehicle(authToken) } catch (error) { @@ -163,7 +168,10 @@ function createWindow() { await tesla.wakeUp(authToken, vehicle.vehicleID) return } - const vehicleData = await tesla.vehicleData(authToken, vehicle.vehicleID) + const vehicleData = await tesla.vehicleData( + authToken, + vehicle.vehicleID + ) mainWindow.webContents.send('tesla-data', { model: vehicle.model, ...vehicleData @@ -177,18 +185,18 @@ function createWindow() { } // Polling function - let poller; + let poller const startPoller = () => { // Set 10s timeout between polls - poller = new Poller(10000); + poller = new Poller(10000) // Wait till the timeout sent our event to the EventEmitter poller.onPoll(async () => { await getTeslaData() - poller.poll(); - }); + poller.poll() + }) // Initial start if (store.get('authToken')) { - poller.poll(); + poller.poll() } } @@ -206,8 +214,8 @@ function createWindow() { // wait function async function wait(ms) { return new Promise(resolve => { - setTimeout(resolve, ms); - }); + setTimeout(resolve, ms) + }) } ipcMain.on('door', async (event, action) => { @@ -234,10 +242,16 @@ function createWindow() { try { if (action === 'climate-on') { log.info('triggered climate start') - await tesla.climateStart(store.get('authToken'), store.get('vehicleId')) + await tesla.climateStart( + store.get('authToken'), + store.get('vehicleId') + ) } else { log.info('triggering climate stop') - await tesla.climateStop(store.get('authToken'), store.get('vehicleId')) + await tesla.climateStop( + store.get('authToken'), + store.get('vehicleId') + ) } await wait(500) await getTeslaData() @@ -255,10 +269,18 @@ function createWindow() { try { if (action === 'sentry-on') { log.info('triggering Sentry On') - await tesla.setSentryMode(store.get('authToken'), store.get('vehicleId'), true) + await tesla.setSentryMode( + store.get('authToken'), + store.get('vehicleId'), + true + ) } else { log.info('triggering Sentry Off') - await tesla.setSentryMode(store.get('authToken'), store.get('vehicleId'), false) + await tesla.setSentryMode( + store.get('authToken'), + store.get('vehicleId'), + false + ) } await wait(500) await getTeslaData() @@ -275,7 +297,11 @@ function createWindow() { mainWindow.webContents.send('action-loading', 'climate-temp') try { log.info('Changing temperature') - await tesla.setTemps(store.get('authToken'), store.get('vehicleId'), temp) + await tesla.setTemps( + store.get('authToken'), + store.get('vehicleId'), + temp + ) await wait(500) await getTeslaData() mainWindow.webContents.send('action-loading', null) @@ -286,10 +312,10 @@ function createWindow() { } }) - const setMenu = () => { contextMenu({ - menu: actions => [{ + menu: actions => [ + { label: `Nikola ${app.getVersion()}`, click() { shell.openExternal('https://github.com/geraldoramos/nikola') @@ -299,8 +325,8 @@ function createWindow() { label: 'Enable Beta Releases', type: 'checkbox', checked: store.get('betaReleases'), - click: function (item) { - if(store.get('betaReleases')){ + click: function(item) { + if (store.get('betaReleases')) { store.set('betaReleases', false) return } @@ -326,7 +352,7 @@ function createWindow() { } } ] - }); + }) } setMenu() @@ -343,12 +369,11 @@ function createWindow() { } getTeslaData() }) - }) // position window to the tray area tray.setIgnoreDoubleClickEvents(true) - tray.on('click', (event) => { + tray.on('click', event => { log.info('click tray event') if (process.platform === 'darwin') { bounds = tray.getBounds() @@ -358,53 +383,41 @@ function createWindow() { }) // and load the index.html of the app. - let indexPath; - if (dev && process.argv.indexOf('--noDevServer') === -1) { + let indexPath + if (isDev) { indexPath = url.format({ protocol: 'http:', host: 'localhost:8080', pathname: 'index.html', slashes: true - }); + }) } else { indexPath = url.format({ protocol: 'file:', pathname: path.join(__dirname, 'dist', 'index.html'), slashes: true - }); + }) } - mainWindow.loadURL(indexPath); + mainWindow.loadURL(indexPath) - // Emitted when the window is closed. - mainWindow.on('closed', function () { + mainWindow.on('closed', function() { log.info('window closed event') - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null; - }); + mainWindow = null + }) } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.on('ready', createWindow); +app.on('ready', createWindow) -// Quit when all windows are closed. app.on('window-all-closed', () => { log.info('window-all-closed event') - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { - app.quit(); + app.quit() } -}); +}) app.on('activate', () => { log.info('app on activate event') - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. if (mainWindow === null) { - createWindow(); + createWindow() } }) diff --git a/package.json b/package.json index e9c7156..de17757 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "dependencies": { "antd": "^3.19.2", "electron-context-menu": "^0.12.1", + "electron-is-dev": "^1.1.0", "electron-log": "^3.0.6", "electron-positioner": "^4.1.0", "electron-store": "^3.2.0", diff --git a/poller.js b/poller.js index bd4020c..d62ca5e 100644 --- a/poller.js +++ b/poller.js @@ -1,21 +1,21 @@ -const EventEmitter = require('events'); +const EventEmitter = require('events') class Poller extends EventEmitter { - /** - * @param {int} timeout how long should we wait after the poll started? - */ - constructor(timeout = 100) { - super(); - this.timeout = timeout; - } + /** + * @param {int} timeout how long should we wait after the poll started? + */ + constructor(timeout = 100) { + super() + this.timeout = timeout + } - poll() { - setTimeout(() => this.emit('poll'), this.timeout); - } + poll() { + setTimeout(() => this.emit('poll'), this.timeout) + } - onPoll(cb) { - this.on('poll', cb); - } + onPoll(cb) { + this.on('poll', cb) + } } -module.exports = Poller; \ No newline at end of file +module.exports = Poller diff --git a/src/components/Actions.js b/src/components/Actions.js index 6f90f87..e3547dd 100644 --- a/src/components/Actions.js +++ b/src/components/Actions.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component } from 'react' import unlock from '../assets/img/unlock.svg' import lock from '../assets/img/lock.svg' import nofan from '../assets/img/nofan.svg' @@ -8,69 +8,94 @@ import sentryOff from '../assets/img/sentry-off.svg' import temp from '../assets/img/temp.svg' class Actions extends React.Component { - render() { - - if(this.props.type === 'door'){ - return ( -
- { this.props.loading === 'door-lock' || this.props.loading === 'door-unlock' ? -
- : -
- } -
- ) - } + if (this.props.type === 'door') { + return ( +
+ {this.props.loading === 'door-lock' || + this.props.loading === 'door-unlock' ? ( +
+ ) : ( +
+ +
+ )} +
+ ) + } - if(this.props.type === 'climate'){ - return ( -
- { this.props.loading === 'climate-on' || this.props.loading === 'climate-off'? -
- : -
- } -
- ) - } + if (this.props.type === 'climate') { + return ( +
+ {this.props.loading === 'climate-on' || + this.props.loading === 'climate-off' ? ( +
+ ) : ( +
+ +
+ )} +
+ ) + } - if(this.props.type === 'sentryMode'){ - return ( -
- { this.props.loading === 'sentry-on' || this.props.loading === 'sentry-off'? -
- : -
- } -
- ) - } + if (this.props.type === 'sentryMode') { + return ( +
+ {this.props.loading === 'sentry-on' || + this.props.loading === 'sentry-off' ? ( +
+ ) : ( +
+ +
+ )} +
+ ) + } - if(this.props.type === 'climateTemp'){ - return ( -
- { this.props.loading === 'climate-temp' ? -
- : -
} -
- - ) - } + if (this.props.type === 'climateTemp') { + return ( +
+ {this.props.loading === 'climate-temp' ? ( +
+ ) : ( +
+ +
+ )} +
+ ) + } } } -export default Actions; +export default Actions diff --git a/src/components/App.js b/src/components/App.js index 4928a99..606cd24 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,9 +1,14 @@ -import modelImage from './helpers/model-image' -import React, { Component } from 'react'; +import modelImage from './helpers/model-image' +import React, { Component } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faTemperatureLow, faTachometerAlt, faBed, faPowerOff, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons' +import { + faTemperatureLow, + faTachometerAlt, + faBed, + faPowerOff, + faExternalLinkAlt +} from '@fortawesome/free-solid-svg-icons' import Maps from './Maps' -const {ipcRenderer, remote} = window.require('electron') import Actions from './Actions' import ReactTooltip from 'react-tooltip' import cToF from './helpers/c-to-f' @@ -11,20 +16,20 @@ import fToC from './helpers/f-to-c' import mToKm from './helpers/m-to-km' import Modal from './Modal' const { shell } = require('electron') +const { ipcRenderer, remote } = window.require('electron') class App extends React.Component { - constructor(props) { - super(props); + super(props) this.state = { climateTempShowModal: false - }; - this.handleLockClick = this.handleLockClick.bind(this); - this.handleFanClick = this.handleFanClick.bind(this); - this.handleClimateTempClick = this.handleClimateTempClick.bind(this); - this.handleClimateTempOk = this.handleClimateTempOk.bind(this); - this.handleClimateTempCancel = this.handleClimateTempCancel.bind(this); - this.handleOpenMapClick = this.handleOpenMapClick.bind(this); + } + this.handleLockClick = this.handleLockClick.bind(this) + this.handleFanClick = this.handleFanClick.bind(this) + this.handleClimateTempClick = this.handleClimateTempClick.bind(this) + this.handleClimateTempOk = this.handleClimateTempOk.bind(this) + this.handleClimateTempCancel = this.handleClimateTempCancel.bind(this) + this.handleOpenMapClick = this.handleOpenMapClick.bind(this) } handleLockClick(event) { @@ -39,124 +44,279 @@ class App extends React.Component { ipcRenderer.send('sentryMode', event.target.name) } - handleClimateTempClick(){ - this.setState({climateTempShowModal:true}) + handleClimateTempClick() { + this.setState({ climateTempShowModal: true }) } - handleOpenMapClick(){ - shell.openExternal(`https://www.google.com/maps/search/?api=1&query=${this.props.status.location.lat},${this.props.status.location.lng}`) + handleOpenMapClick() { + shell.openExternal( + `https://www.google.com/maps/search/?api=1&query=${this.props.status.location.lat},${this.props.status.location.lng}` + ) } - handleClimateTempOk(temp){ - if(temp.match(/^-{0,1}\d+$/)){ - this.setState({climateTempShowModal:false}) - const temperature = this.props.vehicle.temperatureUnits === 'F' ? fToC(temp) : temp + handleClimateTempOk(temp) { + if (temp.match(/^-{0,1}\d+$/)) { + this.setState({ climateTempShowModal: false }) + const temperature = + this.props.vehicle.temperatureUnits === 'F' ? fToC(temp) : temp ipcRenderer.send('climateTemp', temperature) return } - this.setState({climateTempModalError:'Climate temperature needs to be a number'}) + this.setState({ + climateTempModalError: 'Climate temperature needs to be a number' + }) setTimeout(() => { - this.setState({climateTempModalError:null}) - }, 2000); - + this.setState({ climateTempModalError: null }) + }, 2000) } - handleClimateTempCancel(){ - this.setState({climateTempShowModal:false}) + handleClimateTempCancel() { + this.setState({ climateTempShowModal: false }) } - render() { - - if(this.props.vehicle && this.props.vehicle.state === 'asleep'){ - return ( + if (this.props.vehicle && this.props.vehicle.state === 'asleep') { + return (
-
-
Trying to wake up
-
Make sure to close other Tesla Apps
-
+
+
- ) +
Trying to wake up
+
+
+ Make sure to close other Tesla Apps +
+
+
+
+ ) } - if(this.props.vehicle && this.props.vehicle.state === 'offline'){ + if (this.props.vehicle && this.props.vehicle.state === 'offline') { return ( -
-
-
Vehicle is Offline
-
Internet connection in the car is down
-
+
+
+ +
+
Vehicle is Offline
+
+
+ Internet connection in the car is down +
+
+
) - } + } - if(this.props.loading){ + if (this.props.loading) { return ( -
-
-
+
+
+
) } return ( -
- -
-
- Tesla {this.props.vehicle.model} -
- -
Car version: {this.props.status.carVersion}
-
Charging state: {this.props.status.chargingState}
-
Battery level: {`${this.props.status.batteryLevel}%`}
-
Time to full charge: {`${this.props.status.timetoFullCharge} hours`}
-
Door: {this.props.status.locked ? 'Locked' : 'Unlocked'}
-
Climate: {this.props.status.climate ? 'ON' : 'OFF'}
-
Passenger temp. setting: {this.props.vehicle.temperatureUnits === 'F' ? Math.round(cToF(this.props.status.passengerTempSetting)) + ` ${this.props.vehicle.temperatureUnits}` : Math.round(this.props.status.passengerTempSetting) + ` ${this.props.vehicle.temperatureUnits}`}
-
Driver temp. setting: {this.props.vehicle.temperatureUnits === 'F' ? Math.round(cToF(this.props.status.driverTempSetting)) + ` ${this.props.vehicle.temperatureUnits}` : Math.round(this.props.status.passengerTempSetting) + ` ${this.props.vehicle.temperatureUnits}` }
-
Odometer: {this.props.vehicle.distanceUnits.split('/')[0] ==='km' ? Math.round(mToKm(this.props.status.odometer)) : Math.round(this.props.status.odometer) + ` ${this.props.vehicle.distanceUnits.split('/')[0].toUpperCase()}`}
-
Sentry mode: {this.props.status.sentryMode ? 'ON' : 'OFF'}
-
Valet mode: {this.props.status.valetMode ? 'ON' : 'OFF' }
-
- -
-
-
- {!this.props.status.speed ? 'Stopped' : (this.props.vehicle.distanceUnits.split('/')[0] === 'km' ? Math.round(mToKm(this.props.status.speed)) : Math.round(this.props.status.speed))} - {this.props.status.speed ? ` ${this.props.vehicle.distanceUnits}` : '' } -
-
- {(this.props.vehicle.distanceUnits.split('/')[0] === 'km' ? Math.round(mToKm(this.props.status.batteryRange)) : Math.round(this.props.status.batteryRange))} - {this.props.vehicle.distanceUnits.split('/')[0]} -
-
- {this.props.vehicle.temperatureUnits === 'F' ? Math.round(cToF(this.props.status.temperature)) : Math.round(this.props.status.temperature) } - {this.props.vehicle.temperatureUnits} -
+
+ +
+
Tesla {this.props.vehicle.model}
+ +
+ Car version: {this.props.status.carVersion}
-
-
-
Controls
-
{this.props.actionError? 'Action Failed': null}
-
- - - {this.props.status.sentryModeAvailable ? : null} - - +
+ Charging state: {this.props.status.chargingState}
+
+ Battery level: {`${this.props.status.batteryLevel}%`}
-
-
-
Location
- +
+ Time to full charge:{' '} + {`${this.props.status.timetoFullCharge} hours`}
+
+ Door:{' '} + {this.props.status.locked ? 'Locked' : 'Unlocked'} +
+
+ Climate: {this.props.status.climate ? 'ON' : 'OFF'} +
+
+ Passenger temp. setting:{' '} + + {this.props.vehicle.temperatureUnits === 'F' + ? Math.round(cToF(this.props.status.passengerTempSetting)) + + ` ${this.props.vehicle.temperatureUnits}` + : Math.round(this.props.status.passengerTempSetting) + + ` ${this.props.vehicle.temperatureUnits}`} + +
+
+ Driver temp. setting:{' '} + + {this.props.vehicle.temperatureUnits === 'F' + ? Math.round(cToF(this.props.status.driverTempSetting)) + + ` ${this.props.vehicle.temperatureUnits}` + : Math.round(this.props.status.passengerTempSetting) + + ` ${this.props.vehicle.temperatureUnits}`} + +
+
+ Odometer:{' '} + + {this.props.vehicle.distanceUnits.split('/')[0] === 'km' + ? Math.round(mToKm(this.props.status.odometer)) + : Math.round(this.props.status.odometer) + + ` ${this.props.vehicle.distanceUnits + .split('/')[0] + .toUpperCase()}`} + +
+
+ Sentry mode:{' '} + {this.props.status.sentryMode ? 'ON' : 'OFF'} +
+
+ Valet mode:{' '} + {this.props.status.valetMode ? 'ON' : 'OFF'} +
+ + + + +
+
+
+ + + + {' '} + {!this.props.status.speed + ? 'Stopped' + : this.props.vehicle.distanceUnits.split('/')[0] === 'km' + ? Math.round(mToKm(this.props.status.speed)) + : Math.round(this.props.status.speed)} + + + {' '} + {this.props.status.speed + ? ` ${this.props.vehicle.distanceUnits}` + : ''} + + +
+
+ + + + {' '} + {this.props.vehicle.distanceUnits.split('/')[0] === 'km' + ? Math.round(mToKm(this.props.status.batteryRange)) + : Math.round(this.props.status.batteryRange)} + + + {' '} + {this.props.vehicle.distanceUnits.split('/')[0]} + + +
+
+ + + + {' '} + {this.props.vehicle.temperatureUnits === 'F' + ? Math.round(cToF(this.props.status.temperature)) + : Math.round(this.props.status.temperature)} + + + {' '} + {this.props.vehicle.temperatureUnits} + + +
+
+
+
+
Controls
+
+ {this.props.actionError ? 'Action Failed' : null} +
+
+ + + {this.props.status.sentryModeAvailable ? ( + + ) : null} + +
+
+
+
+
+ Location{' '} + + + +
+ +
- ); + ) } } -export default App; +export default App diff --git a/src/components/Home.js b/src/components/Home.js index 630af09..b739cf9 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,173 +1,221 @@ -import '../assets/css/Photon.css'; -import '../assets/css/App.css'; -import React, { Component } from 'react'; -const {ipcRenderer, remote} = window.require('electron') +import '../assets/css/Photon.css' +import '../assets/css/App.css' +import React, { Component } from 'react' +const { ipcRenderer, remote } = window.require('electron') import batteryLevelIcon from './helpers/battery-level-icon' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faWifi } from '@fortawesome/free-solid-svg-icons' -import App from './App'; -import Login from './Login'; +import App from './App' +import Login from './Login' class Home extends React.Component { + constructor(props) { + super(props) + this.state = { + firstData: false, + loading: true, + actionLoading: null, + error: false, + actionError: null, + auth: false, + online: true + } + this.alertOnlineStatus = this.alertOnlineStatus.bind(this) + } - constructor(props) { - super(props); - this.state = { - firstData: false, - loading: true, - actionLoading: null, - error: false, - actionError: null, - auth:false, - online: true, - } - this.alertOnlineStatus = this.alertOnlineStatus.bind(this); - } + alertOnlineStatus() { + this.setState({ online: navigator.onLine }) + } + + componentDidMount() { + window.onerror = function(error, url, line) { + ipcRenderer.send('errorInWindow', error) + } + + window.addEventListener('online', this.alertOnlineStatus) + window.addEventListener('offline', this.alertOnlineStatus) + + this.alertOnlineStatus() - alertOnlineStatus() { - this.setState({online:navigator.onLine}) + ipcRenderer.once('platform', function(event, platform) { + if (platform !== 'darwin') { + document.querySelector('.header-arrow').style = 'display: none' + document.querySelector('.toolbar').style = + '-webkit-app-region: drag;min-height: 10px' } + }) + + ipcRenderer.on( + 'login', + function(event, isLogged) { + if (isLogged) { + this.setState({ auth: true }) + return + } + this.setState({ auth: false }) + }.bind(this) + ) - componentDidMount() { - - window.onerror = function(error, url, line) { - ipcRenderer.send('errorInWindow', error); - }; - - window.addEventListener('online', this.alertOnlineStatus) - window.addEventListener('offline', this.alertOnlineStatus) - - this.alertOnlineStatus() - - ipcRenderer.once('platform', function (event, platform) { - if(platform!=='darwin'){ - document.querySelector('.header-arrow').style = 'display: none' - document.querySelector('.toolbar').style = '-webkit-app-region: drag;min-height: 10px' - } - }) + ipcRenderer.on( + 'action-loading', + function(event, actionLoading) { + this.setState({ actionLoading }) + }.bind(this) + ) - ipcRenderer.on('login', function (event, isLogged) { - if(isLogged){ - this.setState({auth:true}) - return - } - this.setState({auth:false}) - - }.bind(this)) - - ipcRenderer.on('action-loading', function (event, actionLoading) { - this.setState({actionLoading}) - }.bind(this)) - - ipcRenderer.on('action-error', function (event, actionError) { - this.setState({actionError}) - setTimeout(() => { - this.setState({actionError:null}) - }, 3000); - }.bind(this)) - - ipcRenderer.on('tesla-data-error', function (event,store) { - if(!this.state.firstData){ - this.setState({ - error:store - }) - } - - }.bind(this)) - - ipcRenderer.on('tesla-data', function (event,store) { + ipcRenderer.on( + 'action-error', + function(event, actionError) { + this.setState({ actionError }) + setTimeout(() => { + this.setState({ actionError: null }) + }, 3000) + }.bind(this) + ) + + ipcRenderer.on( + 'tesla-data-error', + function(event, store) { + if (!this.state.firstData) { this.setState({ - loading:false, - firstData:true, - batteryIcon: batteryLevelIcon(store.charge_state ? store.charge_state.battery_level : 'default'), - vehicle:{ - model: store.model, - state: store.state, - temperatureUnits: store.gui_settings? store.gui_settings.gui_temperature_units : null, - distanceUnits: store.gui_settings? store.gui_settings.gui_distance_units : '', - chargeRateUnits: store.gui_settings? store.gui_settings.gui_charge_rate_units : null + error: store + }) + } + }.bind(this) + ) + ipcRenderer.on( + 'tesla-data', + function(event, store) { + this.setState({ + loading: false, + firstData: true, + batteryIcon: batteryLevelIcon( + store.charge_state ? store.charge_state.battery_level : 'default' + ), + vehicle: { + model: store.model, + state: store.state, + temperatureUnits: store.gui_settings + ? store.gui_settings.gui_temperature_units + : null, + distanceUnits: store.gui_settings + ? store.gui_settings.gui_distance_units + : '', + chargeRateUnits: store.gui_settings + ? store.gui_settings.gui_charge_rate_units + : null + }, + status: { + driverTempSetting: store.climate_state + ? store.climate_state.driver_temp_setting + : null, + passengerTempSetting: store.climate_state + ? store.climate_state.passenger_temp_setting + : null, + carVersion: store.vehicle_state + ? store.vehicle_state.car_version + : null, + batteryRange: store.charge_state + ? store.charge_state.battery_range + : null, + batteryLevel: store.charge_state + ? store.charge_state.battery_level + : null, + locked: store.vehicle_state ? store.vehicle_state.locked : null, + odometer: store.vehicle_state ? store.vehicle_state.odometer : null, + sentryMode: store.vehicle_state + ? store.vehicle_state.sentry_mode + : null, + sentryModeAvailable: store.vehicle_state + ? store.vehicle_state.sentry_mode_available + : null, + valetMode: store.vehicle_state + ? store.vehicle_state.valet_mode + : null, + climate: store.climate_state + ? store.climate_state.is_climate_on + : null, + speed: store.drive_state ? store.drive_state.speed : null, + chargingState: store.charge_state + ? store.charge_state.charging_state + : null, + timetoFullCharge: store.charge_state + ? store.charge_state.time_to_full_charge + : null, + temperature: store.climate_state + ? store.climate_state.inside_temp + : null, + location: { + lat: store.drive_state ? store.drive_state.latitude : null, + lng: store.drive_state ? store.drive_state.longitude : null }, - status:{ - driverTempSetting: store.climate_state ? store.climate_state.driver_temp_setting: null, - passengerTempSetting: store.climate_state ? store.climate_state.passenger_temp_setting: null, - carVersion: store.vehicle_state? store.vehicle_state.car_version: null, - batteryRange: store.charge_state ? store.charge_state.battery_range: null, - batteryLevel: store.charge_state ? store.charge_state.battery_level: null, - locked: store.vehicle_state? store.vehicle_state.locked : null, - odometer:store.vehicle_state? store.vehicle_state.odometer : null, - sentryMode:store.vehicle_state? store.vehicle_state.sentry_mode : null, - sentryModeAvailable:store.vehicle_state? store.vehicle_state.sentry_mode_available : null, - valetMode:store.vehicle_state? store.vehicle_state.valet_mode : null, - climate: store.climate_state ? store.climate_state.is_climate_on : null, - speed: store.drive_state? store.drive_state.speed : null, - chargingState: store.charge_state ? store.charge_state.charging_state : null, - timetoFullCharge:store.charge_state ? store.charge_state.time_to_full_charge : null, - temperature: store.climate_state ? store.climate_state.inside_temp: null, - location: {lat: store.drive_state?store.drive_state.latitude:null, lng: store.drive_state? store.drive_state.longitude : null}, - locationAsOf: store.drive_state? store.drive_state.gps_as_of : null - } - }); - }.bind(this)); - } + locationAsOf: store.drive_state ? store.drive_state.gps_as_of : null + } + }) + }.bind(this) + ) + } - componentWillUnmount(){ - ipcRenderer.removeAllListeners() - window.removeEventListener('online', this.alertOnlineStatus) - window.removeEventListener('offline', this.alertOnlineStatus) - } + componentWillUnmount() { + ipcRenderer.removeAllListeners() + window.removeEventListener('online', this.alertOnlineStatus) + window.removeEventListener('offline', this.alertOnlineStatus) + } render() { - - if(!this.state.online){ + if (!this.state.online) { return (
-
-
-
-
-
-
-
-
No Internet Connection
-
+
+
+
+
+
+
+
+ +
+
No Internet Connection
+
+
+
+
-
-
-
- )} + ) + } - if(!this.state.auth){ + if (!this.state.auth) { return ( -
-
-
-
-
-
- -
-
-
-
- ) - } - - return (
-
+
+
+
+ +
+
+
+
+ ) + } + + return ( +
+
+
+
- -
-
-
+
- ) +
+
+
+ ) } } -export default Home; +export default Home diff --git a/src/components/Login.js b/src/components/Login.js index c3f2cc9..93668ee 100644 --- a/src/components/Login.js +++ b/src/components/Login.js @@ -1,69 +1,101 @@ -import React, { Component } from 'react'; -const {ipcRenderer, remote} = window.require('electron') +import React, { Component } from 'react' +const { ipcRenderer, remote } = window.require('electron') import logo from '../assets/img/logo.svg' class Login extends React.Component { - constructor(props) { - super(props); - this.state = {username: '', password:'', error: null, loading:false}; - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + super(props) + this.state = { username: '', password: '', error: null, loading: false } + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) } - handleChange(event) { + handleChange(event) { const value = event.target.value const name = event.target.name this.setState({ [name]: value - }); + }) } handleSubmit(event) { - event.preventDefault(); - ipcRenderer.send('login-attempt', {username:this.state.username, password:this.state.password}) - this.setState({loading:true}) - ipcRenderer.on('login-failed', function (event,store) { - console.log(store) - this.setState({ - error:store, - loading:false - }) - - }.bind(this)) + event.preventDefault() + ipcRenderer.send('login-attempt', { + username: this.state.username, + password: this.state.password + }) + this.setState({ loading: true }) + ipcRenderer.on( + 'login-failed', + function(event, store) { + console.log(store) + this.setState({ + error: store, + loading: false + }) + }.bind(this) + ) } render() { - - if(this.state.loading){ + if (this.state.loading) { return ( -
-
-
+
+
+
) } - return ( -
-
-
-
-
{this.state.error? 'Login Failed': null}
- - -
-
- - -
-
-

Auth token is stored locally and is not sent anywhere besides Tesla servers. To remove the token from this computer, just logout.

- -
-
-
- ) + return ( +
+
+ +
+
+
+
+ {this.state.error ? 'Login Failed' : null} +
+ + +
+
+ + +
+
+

+ Auth token is stored locally and is not sent anywhere besides + Tesla servers. To remove the token from this computer, just + logout. +

+ +
+
+
+ ) } } -export default Login; +export default Login diff --git a/src/components/Maps.js b/src/components/Maps.js index 0cbd623..3dce2d7 100644 --- a/src/components/Maps.js +++ b/src/components/Maps.js @@ -1,200 +1,207 @@ -import React, { Component } from 'react'; -import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react'; +import React, { Component } from 'react' +import { Map, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react' const mapStyles = { - width: '100%', - height: '220px', - position: 'relative', - margin: '6px auto' - } + width: '100%', + height: '220px', + position: 'relative', + margin: '6px auto' +} export class MapContainer extends Component { - render() { return ( - - + - ); + ) } } export default GoogleApiWrapper({ apiKey: process.env.GOOGLE_MAPS -})(MapContainer) \ No newline at end of file +})(MapContainer) diff --git a/src/components/Modal.js b/src/components/Modal.js index 6e247a6..9782ce4 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -1,36 +1,44 @@ -import React, { Component } from 'react'; -import { Modal } from 'antd'; - +import React, { Component } from 'react' +import { Modal } from 'antd' class Popup extends React.Component { constructor(props) { - super(props); + super(props) this.state = { input: '' - } - this.handleChange = this.handleChange.bind(this); -} - handleChange(e){ - this.setState({ input: e.target.value }); } + this.handleChange = this.handleChange.bind(this) + } + handleChange(e) { + this.setState({ input: e.target.value }) + } - render() { - return ( - this.props.onOk(this.state.input)} - onCancel={this.props.onCancel} - cancelButtonProps={ {type:'ghost'} } + render() { + return ( + this.props.onOk(this.state.input)} + onCancel={this.props.onCancel} + cancelButtonProps={{ type: 'ghost' }} + > +
-
{!this.props.errorMessage ? this.props.info : this.props.errorMessage}
-
- + {!this.props.errorMessage ? this.props.info : this.props.errorMessage}
- +
+ +
+ ) - } -}; -export default Popup; \ No newline at end of file + } +} +export default Popup diff --git a/src/components/helpers/battery-level-icon.js b/src/components/helpers/battery-level-icon.js index 93df2cd..d1f656e 100644 --- a/src/components/helpers/battery-level-icon.js +++ b/src/components/helpers/battery-level-icon.js @@ -1,22 +1,28 @@ -import { faBatteryFull, faBatteryEmpty, faBatteryQuarter, faBatteryHalf, faBatteryThreeQuarters } from '@fortawesome/free-solid-svg-icons' +import { + faBatteryFull, + faBatteryEmpty, + faBatteryQuarter, + faBatteryHalf, + faBatteryThreeQuarters +} from '@fortawesome/free-solid-svg-icons' -export default (level) =>{ - let battery; - switch (true) { - case (0 <= level && level <= 9): - battery = {type: faBatteryEmpty, color:'#cc0001'} - break - case (10 <= level && level <= 39): - battery = {type: faBatteryQuarter, color:'#cc0001'} - break - case (40 <= level && level <= 69): - battery = {type: faBatteryHalf, color:'#1BC47D'} - break - case (70 <= level && level <= 90): - battery = {type: faBatteryThreeQuarters, color:'#1BC47D'} - break - default: - battery = {type: faBatteryFull, color:'#1BC47D'} - } - return battery - } \ No newline at end of file +export default level => { + let battery + switch (true) { + case 0 <= level && level <= 9: + battery = { type: faBatteryEmpty, color: '#cc0001' } + break + case 10 <= level && level <= 39: + battery = { type: faBatteryQuarter, color: '#cc0001' } + break + case 40 <= level && level <= 69: + battery = { type: faBatteryHalf, color: '#1BC47D' } + break + case 70 <= level && level <= 90: + battery = { type: faBatteryThreeQuarters, color: '#1BC47D' } + break + default: + battery = { type: faBatteryFull, color: '#1BC47D' } + } + return battery +} diff --git a/src/components/helpers/c-to-f.js b/src/components/helpers/c-to-f.js index 610f6e4..fc3c6a8 100644 --- a/src/components/helpers/c-to-f.js +++ b/src/components/helpers/c-to-f.js @@ -1,4 +1,4 @@ export default function cToF(celsius) { - const fahrenheit = (celsius * (9/5)) + 32 - return fahrenheit; - } \ No newline at end of file + const fahrenheit = celsius * (9 / 5) + 32 + return fahrenheit +} diff --git a/src/components/helpers/f-to-c.js b/src/components/helpers/f-to-c.js index 2ea8069..af956ff 100644 --- a/src/components/helpers/f-to-c.js +++ b/src/components/helpers/f-to-c.js @@ -1,4 +1,4 @@ export default function fToC(fahrenheit) { - const celsius = (fahrenheit - 32) * 5 / 9 - return celsius; - } \ No newline at end of file + const celsius = ((fahrenheit - 32) * 5) / 9 + return celsius +} diff --git a/src/components/helpers/m-to-km.js b/src/components/helpers/m-to-km.js index 21e09ad..57a7507 100644 --- a/src/components/helpers/m-to-km.js +++ b/src/components/helpers/m-to-km.js @@ -1,4 +1,4 @@ export default function mToKm(miles) { - const km = miles * 1.60934; - return km - } \ No newline at end of file + const km = miles * 1.60934 + return km +} diff --git a/src/components/helpers/model-image.js b/src/components/helpers/model-image.js index 78ed019..a67e20c 100644 --- a/src/components/helpers/model-image.js +++ b/src/components/helpers/model-image.js @@ -3,28 +3,28 @@ import models from '../../assets/img/models.png' import modely from '../../assets/img/modely.png' import modelx from '../../assets/img/modelx.png' -export default (model) =>{ - let img; - if(!model){ - img = model3 - return img - } - model = model.toLowerCase() - switch (true) { - case (model === 'model 3'): - img = model3 - break - case (model === 'model s'): - img = models - break - case (model === 'model y'): - img = modely - break - case (model === 'model x'): - img = modelx - break - default: - img = model3 - } +export default model => { + let img + if (!model) { + img = model3 return img } + model = model.toLowerCase() + switch (true) { + case model === 'model 3': + img = model3 + break + case model === 'model s': + img = models + break + case model === 'model y': + img = modely + break + case model === 'model x': + img = modelx + break + default: + img = model3 + } + return img +} diff --git a/src/index.js b/src/index.js index 4dcf897..4aee25b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,11 @@ -import React from 'react'; -import { render } from 'react-dom'; -import Home from './components/Home'; +import React from 'react' +import { render } from 'react-dom' +import Home from './components/Home' // Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it -let root = document.createElement('div'); -root.id = "root"; -document.body.appendChild( root ); +let root = document.createElement('div') +root.id = 'root' +document.body.appendChild(root) // Now we can render our application into it -render( , document.getElementById('root') ); +render(, document.getElementById('root')) diff --git a/yarn.lock b/yarn.lock index a5303d1..fa0e9d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2367,7 +2367,7 @@ electron-download@^4.1.0, electron-download@^4.1.1: semver "^5.4.1" sumchecker "^2.0.2" -electron-is-dev@^1.0.1: +electron-is-dev@^1.0.1, electron-is-dev@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.1.0.tgz#b15a2a600bdc48a51a857d460e05f15b19a2522c" integrity sha512-Z1qA/1oHNowGtSBIcWk0pcLEqYT/j+13xUw/MYOrBUOL4X7VN0i0KCTf5SqyvMPmW5pSPKbo28wkxMxzZ20YnQ== @@ -4690,11 +4690,6 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "0.0.4" -node-machine-id@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" - integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== - node-pre-gyp@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" @@ -6166,7 +6161,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.45.0, request@^2.81.0, request@^2.88.0: +request@^2.45.0, request@^2.81.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -7200,15 +7195,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -universal-analytics@^0.4.20: - version "0.4.20" - resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.20.tgz#d6b64e5312bf74f7c368e3024a922135dbf24b03" - integrity sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw== - dependencies: - debug "^3.0.0" - request "^2.88.0" - uuid "^3.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -7345,7 +7331,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==