diff --git a/README.md b/README.md index 17cec7a..6318386 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## Downloads for v1.5.0 You may view all the releases [here](https://github.com/vinnymac/PokeNurse/releases) -* [Mac OS X](https://github.com/vinnymac/PokeNurse/releases/download/v1.5.0/PokeNurse-darwin-x64.zip) +* [macOS](https://github.com/vinnymac/PokeNurse/releases/download/v1.5.0/PokeNurse-darwin-x64.zip) * [Windows 32 bit](https://github.com/vinnymac/PokeNurse/releases/download/v1.5.0/PokeNurse-win32-ia32.zip) * [Windows 64 bit](https://github.com/vinnymac/PokeNurse/releases/download/v1.5.0/PokeNurse-win32-x64.zip) * [Linux 32 bit](https://github.com/vinnymac/PokeNurse/releases/download/v1.5.0/PokeNurse-linux-ia32.zip) diff --git a/app/actions/status.js b/app/actions/status.js index 2191c97..43e1412 100644 --- a/app/actions/status.js +++ b/app/actions/status.js @@ -2,7 +2,10 @@ import { createAction } from 'redux-actions' +const updateStatus = createAction('UPDATE_STATUS') +const resetStatus = createAction('RESET_STATUS') + export default { - updateStatus: createAction('UPDATE_STATUS'), - resetStatus: createAction('RESET_STATUS') + updateStatus, + resetStatus, } diff --git a/app/actions/trainer.js b/app/actions/trainer.js index 59c8053..3760754 100644 --- a/app/actions/trainer.js +++ b/app/actions/trainer.js @@ -7,6 +7,10 @@ import { } from 'lodash' import pogobuf from 'pogobuf' import POGOProtos from 'node-pogo-protos' +import { + ipcRenderer +} from 'electron' +import moment from 'moment' import client from '../client' @@ -14,6 +18,11 @@ import client from '../client' import utils from '../utils' import baseStats from '../../baseStats' +import { + updateStatus, + resetStatus, +} from './status' + // Maybe put this info and the helper methods in utils? const kantoDexCount = 151 @@ -253,6 +262,7 @@ function transferPokemon(pokemon, delay) { dispatch(transferPokemonSuccess(pokemon)) } catch (error) { dispatch(transferPokemonFailed(error)) + throw error } } } @@ -265,12 +275,106 @@ function evolvePokemon(pokemon, delay) { dispatch(evolvePokemonSuccess(pokemon)) } catch (error) { dispatch(evolvePokemonFailed(error)) + throw error } } } +function promiseChainFromArray(array, iterator) { + let promise = Promise.resolve() + + array.forEach((value, index) => { + promise = promise.then(() => iterator(value, index)) + }) + + return promise +} + +function randomDelay([min, max]) { + return Math.round((min + Math.random() * (max - min)) * 1000) +} + +function average(arr) { + const sum = arr.reduce((result, currentValue) => + result + currentValue + , 0) + + return sum / arr.length +} + +const updateMonster = createAction('UPDATE_MONSTER') + +function processSelectedPokemon(selectedPokemon, method, action, time, delayRange, done) { + return async (dispatch) => { + dispatch(updateStatus({ + selectedPokemon, + method, + time, + })) + + let startTime = moment() + const responseTimesInSeconds = [] + + promiseChainFromArray(selectedPokemon, (pokemon, index) => + dispatch(action(pokemon, index * randomDelay(delayRange))) + .then(() => { + let statusUpdates = { current: pokemon } + + // Calculate the Estimated Time in Seconds Left + const requestLatencyInSeconds = moment().diff(startTime, 'seconds') + startTime = moment() + + if (requestLatencyInSeconds > 0) { + responseTimesInSeconds.push(requestLatencyInSeconds) + const averageRequestLatencyInSeconds = average(responseTimesInSeconds) + + const numberOfJobsLeft = selectedPokemon.length - (index + 1) + const estimatedSecondsLeft = numberOfJobsLeft * averageRequestLatencyInSeconds + + statusUpdates = Object.assign({}, statusUpdates, { time: estimatedSecondsLeft }) + } + + dispatch(updateStatus(statusUpdates)) + + dispatch(updateMonster({ + pokemon, + options: { remove: true } + })) + }) + ).then(() => { + done() + ipcRenderer.send('information-dialog', 'Complete!', `Finished ${method}`) + dispatch(resetStatus()) + dispatch(getTrainerPokemon()) + }).catch(error => { + done() + ipcRenderer.send('error-message', `Error while running ${method.toLowerCase()}:\n\n${error}`) + dispatch(resetStatus()) + dispatch(getTrainerPokemon()) + }) + } +} + +function transferSelectedPokemon(selectedPokemon, done) { + const method = 'Transfer' + const time = selectedPokemon.length * 2.5 + const delayRange = [2, 3] + const action = transferPokemon + + return processSelectedPokemon(selectedPokemon, method, action, time, delayRange, done) +} + +function evolveSelectedPokemon(selectedPokemon, done) { + const method = 'Evolve' + const time = selectedPokemon.length * 27.5 + const delayRange = [25, 30] + const action = evolvePokemon + + return processSelectedPokemon(selectedPokemon, method, action, time, delayRange, done) +} + export default { - updateMonster: createAction('UPDATE_MONSTER'), + updateMonster, updateSpecies: createAction('UPDATE_SPECIES'), updateMonsterSort: createAction('UPDATE_MONSTER_SORT'), sortSpecies: createAction('SORT_SPECIES'), @@ -286,4 +390,6 @@ export default { renamePokemon, transferPokemon, evolvePokemon, + evolveSelectedPokemon, + transferSelectedPokemon, } diff --git a/app/css/pokenurse.css b/app/css/pokenurse.css index a771809..795fafb 100644 --- a/app/css/pokenurse.css +++ b/app/css/pokenurse.css @@ -38,17 +38,30 @@ .bm-item-list ul { float: none; display: block; + list-style: none; + width: 100%; } .bm-menu .bm-item-list li { float: none; display: block; padding: 5px 0px; - border-bottom: 1px solid #b8b7ad; + border-top: 1px solid #b8b7ad; + width: 100%; +} +.bm-menu .bm-item-list li:first-of-type, li:last-of-type { + border-top: none; } .bm-menu .bm-item-list li a { padding: 5px 0px; cursor: pointer; + color: #9d9d9d; + text-decoration: none; + line-height: 20px; +} + +.bm-menu .bm-item-list li a:hover, a:active { + color: white; } .bm-menu .bm-item-list i { @@ -56,6 +69,10 @@ float: right; } +.bm-menu .bm-item-list .submenu li { + background-color: rgba(35, 35, 35, 0.4); +} + /* Morph shape necessary with bubble or elastic */ .bm-morph-shape { fill: #373a47; diff --git a/app/imgs/egg.png b/app/imgs/egg.png new file mode 100644 index 0000000..5ca7920 Binary files /dev/null and b/app/imgs/egg.png differ diff --git a/app/reducers/status.js b/app/reducers/status.js index d8c9dc7..9d749f2 100644 --- a/app/reducers/status.js +++ b/app/reducers/status.js @@ -7,7 +7,6 @@ const initialState = { current: null, time: 0, method: '', - finished: null } export default handleActions({ diff --git a/app/screens/ConfirmationDialog/components/SelectedPokemon.js b/app/screens/ConfirmationDialog/components/SelectedPokemon.js index 9aad147..7834f3f 100644 --- a/app/screens/ConfirmationDialog/components/SelectedPokemon.js +++ b/app/screens/ConfirmationDialog/components/SelectedPokemon.js @@ -28,7 +28,7 @@ const SelectedPokemon = React.createClass({ - { this.buildRows(pokemon) } + { this.buildRows(pokemon) } diff --git a/app/screens/Menu/components/Eggs.js b/app/screens/Menu/components/Eggs.js new file mode 100644 index 0000000..69afef6 --- /dev/null +++ b/app/screens/Menu/components/Eggs.js @@ -0,0 +1,29 @@ +import React, { + PropTypes +} from 'react' + +const Eggs = React.createClass({ + propTypes: { + eggList: PropTypes.array, + }, + + render() { + const { + eggList + } = this.props + + const eggs = eggList.map((e, i) => +
  • + {e.egg_km_walked_start}.0 / {e.egg_km_walked_target}.0 km +
  • + ) + + return ( + + ) + } +}) + +export default Eggs diff --git a/app/screens/Menu/index.js b/app/screens/Menu/index.js index 9dcba46..7df0314 100644 --- a/app/screens/Menu/index.js +++ b/app/screens/Menu/index.js @@ -9,26 +9,37 @@ import { logout } from '../../actions' import renderSettings from '../Settings' +// import Eggs from './components/Eggs' const Menu = require('react-burger-menu').slide const MainMenu = React.createClass({ propTypes: { updateStatus: PropTypes.func.isRequired, - logout: PropTypes.func.isRequired + logout: PropTypes.func.isRequired, + eggs: PropTypes.array, }, render() { + // const { + // eggs + // } = this.props + return (
    diff --git a/app/screens/Table/components/Species.js b/app/screens/Table/components/Species.js index 3be278f..f4e4193 100644 --- a/app/screens/Table/components/Species.js +++ b/app/screens/Table/components/Species.js @@ -45,10 +45,10 @@ const Species = React.createClass({
    - {this.getSpeciesHeader()} + {this.getSpeciesHeader()} - {this.getSpeciesBody(monsters.species)} + {this.getSpeciesBody(monsters.species)}
    diff --git a/app/screens/Table/components/SpeciesPokemonCounter.js b/app/screens/Table/components/SpeciesPokemonCounter.js index ca0983b..673df99 100644 --- a/app/screens/Table/components/SpeciesPokemonCounter.js +++ b/app/screens/Table/components/SpeciesPokemonCounter.js @@ -17,7 +17,7 @@ const SpeciesPokemonCounter = React.createClass({ return ( - Species: {this.handleSpeciesRecount(monsters)} + Species: {this.handleSpeciesRecount(monsters)} {' | '} diff --git a/app/screens/Table/components/Status.js b/app/screens/Table/components/Status.js index a23b27c..9badfd3 100644 --- a/app/screens/Table/components/Status.js +++ b/app/screens/Table/components/Status.js @@ -4,26 +4,14 @@ import React, { import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' import Progress from './Progress' -import { updateStatus, resetStatus } from '../../../actions' - const Status = React.createClass({ displayName: 'Status', propTypes: { status: PropTypes.object, - resetStatus: PropTypes.func.isRequired, - updateStatus: PropTypes.func.isRequired - }, - - componentDidUpdate(prevProps) { - // TODO add thunks and move interval to redux action creators - if (!prevProps.status.current && this.props.status.current) { - this.countDownInterval() - } }, render() { @@ -52,26 +40,8 @@ const Status = React.createClass({
    ) }, - - countDownInterval() { - let { time } = this.props.status - - const { finished } = this.props.status - - const interval = setInterval(() => { - this.props.updateStatus({ time: time-- }) - - if (time <= 0) { - clearInterval(interval) - finished() - this.props.resetStatus() - } - }, 1000) - } }) export default connect(state => ({ status: state.status -}), (dispatch => bindActionCreators({ - updateStatus, resetStatus -}, dispatch)))(Status) +}), null)(Status) diff --git a/app/screens/Table/index.js b/app/screens/Table/index.js index 883751b..1f4fd4d 100644 --- a/app/screens/Table/index.js +++ b/app/screens/Table/index.js @@ -16,14 +16,12 @@ import CheckCounter from './components/CheckCounter' import confirmDialog from '../ConfirmationDialog' import { - updateStatus, - logout, getTrainerPokemon, updateSpecies, updateMonster, updateMonsterSort, - evolvePokemon, - transferPokemon, + evolveSelectedPokemon, + transferSelectedPokemon, } from '../../actions' window.$ = window.jQuery = $ @@ -41,10 +39,6 @@ function runningCheck() { return false } -function randomDelay(min, max) { - return Math.round((min + Math.random() * (max - min)) * 1000) -} - function getHeaderBackgroundStyles(team) { let teamName = null let teamColor = null @@ -75,9 +69,9 @@ function getHeaderBackgroundStyles(team) { const Table = React.createClass({ propTypes: { - updateStatus: PropTypes.func.isRequired, - logout: PropTypes.func.isRequired, - trainerData: PropTypes.object, + trainerData: PropTypes.shape({ + username: PropTypes.string, + }), getTrainerPokemon: PropTypes.func.isRequired, monsters: PropTypes.object, updateSpecies: PropTypes.func.isRequired, @@ -87,8 +81,8 @@ const Table = React.createClass({ filterBy: PropTypes.string, sortBy: PropTypes.string, sortDir: PropTypes.string, - transferPokemon: PropTypes.func.isRequired, - evolvePokemon: PropTypes.func.isRequired, + evolveSelectedPokemon: PropTypes.func.isRequired, + transferSelectedPokemon: PropTypes.func.isRequired, }, componentDidMount() { @@ -125,7 +119,7 @@ const Table = React.createClass({ style={backgroundHeaderStyles} >
    - +
    {' '} @@ -168,6 +162,7 @@ const Table = React.createClass({ @@ -276,14 +271,9 @@ const Table = React.createClass({ onClickSecondary: () => { if (runningCheck()) return - this.handleCountDown(selectedPokemon, 'Transfer', selectedPokemon.length * 2.5) + running = true - selectedPokemon.forEach((pokemon, index) => { - this.props.transferPokemon(pokemon, index * randomDelay(2, 3)) - .then(() => { - this.handleTransferCompleted(pokemon) - }) - }) + this.props.transferSelectedPokemon(selectedPokemon, this.handleAllComplete) }, primaryText: 'Transfer without favorites', @@ -296,14 +286,9 @@ const Table = React.createClass({ return isntFavorite }) - this.handleCountDown(filteredPokemon, 'Transfer', filteredPokemon.length * 2.5) + running = true - filteredPokemon.forEach((pokemon, index) => { - this.props.transferPokemon(pokemon, index * randomDelay(2, 3)) - .then(() => { - this.handleTransferCompleted(pokemon) - }) - }) + this.props.transferSelectedPokemon(filteredPokemon, this.handleAllComplete) } }) }, @@ -323,47 +308,16 @@ const Table = React.createClass({ onClickPrimary: () => { if (runningCheck()) return - this.handleCountDown(selectedPokemon, 'Evolve', selectedPokemon.length * 27.5) - - selectedPokemon.forEach((pokemon, index) => { - this.props.evolvePokemon(pokemon, index * randomDelay(25, 30)) - .then(() => { - this.handleEvolveCompleted(pokemon) - }) - }) - } - }) - }, + running = true - handleCountDown(selectedPokemon, method, time) { - running = true - - this.props.updateStatus({ - selectedPokemon, - method, - time, - finished: () => { - running = false - ipcRenderer.send('information-dialog', 'Complete!', `Finished ${method}`) - this.handleRefresh() + this.props.evolveSelectedPokemon(selectedPokemon, this.handleAllComplete) } }) }, - removeMonster(pokemon) { - this.updateMonster(pokemon, { remove: true }) - }, - - handleEvolveCompleted(pokemon) { - this.props.updateStatus({ current: pokemon }) - this.removeMonster(pokemon) + handleAllComplete() { + running = false }, - - handleTransferCompleted(pokemon) { - this.props.updateStatus({ current: pokemon }) - this.removeMonster(pokemon) - }, - }) export default connect((state => ({ @@ -374,12 +328,10 @@ export default connect((state => ({ sortDir: state.trainer.sortDir, filterBy: state.trainer.filterBy, })), (dispatch => bindActionCreators({ - updateStatus, - logout, getTrainerPokemon, updateSpecies, updateMonster, updateMonsterSort, - evolvePokemon, - transferPokemon, + evolveSelectedPokemon, + transferSelectedPokemon, }, dispatch)))(Table) diff --git a/main.development.js b/main.development.js index 2e2cfa9..3988299 100644 --- a/main.development.js +++ b/main.development.js @@ -5,11 +5,12 @@ import { dialog, Menu } from 'electron' +import electronLocalshortcut from 'electron-localshortcut' import menuTemplate from './main/mainMenu' import checkForUpdates from './main/checkForUpdates' -const isOSX = process.platform === 'darwin' +const isMacOS = process.platform === 'darwin' const isDevelopment = process.env.NODE_ENV === 'development' let mainWindow = null @@ -20,6 +21,11 @@ process.on('uncaughtException', (error) => { console.error('uncaughtException', error) // eslint-disable-line }) +function preventUnusedElectronDebug() { + // prevent localshortcuts registered by electron-debug that we configure ourselves + electronLocalshortcut.unregister(isMacOS ? 'Cmd+Alt+I' : 'Ctrl+Shift+I') +} + function createWindow() { mainWindow = new BrowserWindow({ width: 800, @@ -29,6 +35,8 @@ function createWindow() { show: false }) + if (isDevelopment) preventUnusedElectronDebug() + mainWindow.loadURL(`file://${__dirname}/app/app.html`) // mainWindow.once('ready-to-show', () => { // win.show() @@ -60,9 +68,9 @@ function createWindow() { // - // OS X Specific Configuration + // macOS Specific Configuration // - if (isOSX) { + if (isMacOS) { // Hide the window on close rather than quitting the app, // and make sure to really close the window when quitting. mainWindow.on('close', (event) => { @@ -94,9 +102,9 @@ function createWindow() { } // -// OS X Specific Configuration +// macOS Specific Configuration // -if (isOSX) { +if (isMacOS) { // Hide the window on close rather than quitting the app, // and make sure to really close the window when quitting. app.on('before-quit', () => { @@ -109,7 +117,7 @@ if (isOSX) { } app.on('window-all-closed', () => { - if (!isOSX) { + if (!isMacOS) { app.quit() } }) diff --git a/main/mainMenu.js b/main/mainMenu.js index 7656a2d..0b924b8 100644 --- a/main/mainMenu.js +++ b/main/mainMenu.js @@ -119,8 +119,17 @@ const template = [ click() { const allWindows = BrowserWindow.getAllWindows() const firstWindow = allWindows[0] - if (firstWindow) { - firstWindow.toggleDevTools() + if (!firstWindow) return + + const isDevToolsOpened = firstWindow.isDevToolsOpened() + const isDevToolsFocused = firstWindow.isDevToolsFocused() + + if (isDevToolsOpened && isDevToolsFocused) { + firstWindow.closeDevTools() + } else if (isDevToolsOpened && !isDevToolsFocused) { + firstWindow.devToolsWebContents.focus() + } else { + firstWindow.openDevTools() } } }, diff --git a/package.js b/package.js index 206d3b4..4b9e031 100644 --- a/package.js +++ b/package.js @@ -11,6 +11,8 @@ const del = require('del') const exec = require('child_process').exec const argv = require('minimist')(process.argv.slice(2)) const pkg = require('./package.json') +const electronPrebuilt = require('electron-prebuilt') +const semver = require('semver') const deps = Object.keys(pkg.dependencies) const devDeps = Object.keys(pkg.devDependencies) @@ -47,13 +49,15 @@ if (version) { startPack() } else { // use the same version as the currently-installed electron-prebuilt - exec('npm view electron-prebuilt version', (err, stdout) => { - if (err) { - DEFAULT_OPTS.version = '1.2.0' - } else { - DEFAULT_OPTS.version = pkg.devDependencies['electron-prebuilt'] + exec(`${electronPrebuilt} --version`, (err, stdout) => { + let electronVersion = semver.clean(stdout) + + if (err || !semver.valid(electronVersion)) { + console.log('Unable to identify Electron version, falling back to v1.2.0.') + electronVersion = '1.2.0' } + DEFAULT_OPTS.version = electronVersion startPack() }) } diff --git a/package.json b/package.json index a1c8ba8..f85dd79 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "homepage": "https://github.com/vinnymac/PokeNurse#readme", "devDependencies": { "asar": "0.12.3", - "async-file": "^2.0.0", + "async-file": "2.0.1", "babel-core": "6.14.0", "babel-eslint": "^6.1.2", "babel-loader": "^6.2.5", @@ -53,32 +53,33 @@ "babel-register": "6.14.0", "concurrently": "^2.2.0", "cross-env": "2.0.1", - "css-loader": "^0.24.0", + "css-loader": "0.25.0", "del": "^2.2.2", "devtron": "^1.3.0", "electron-debug": "^1.0.1", "electron-devtools-installer": "^2.0.1", - "electron-packager": "7.7.0", - "electron-prebuilt": "1.3.5", + "electron-packager": "8.0.0", + "electron-prebuilt": "1.4.0", "electron-rebuild": "1.2.1", "eslint": "3.4.0", - "eslint-config-airbnb": "^10.0.1", - "eslint-import-resolver-webpack": "^0.5.1", - "eslint-plugin-import": "1.14.0", - "eslint-plugin-jsx-a11y": "2.2.1", + "eslint-config-airbnb": "10.0.1", + "eslint-import-resolver-webpack": "0.6.0", + "eslint-plugin-import": "1.15.0", + "eslint-plugin-jsx-a11y": "2.2.2", "eslint-plugin-no-loops": "^0.3.0", - "eslint-plugin-react": "6.2.0", + "eslint-plugin-react": "6.3.0", "express": "^4.14.0", "fbjs-scripts": "^0.7.1", "file-loader": "^0.9.0", "jquery": "^2.2.4", "json-loader": "^0.5.4", - "lodash": "^4.15.0", + "lodash": "4.16.0", "minimist": "^1.2.0", - "react": "15.3.1", + "moment": "2.15.0", + "react": "15.3.2", "react-bootstrap": "^0.30.3", "react-burger-menu": "^1.10.4", - "react-dom": "15.3.1", + "react-dom": "15.3.2", "react-redux": "^4.4.5", "redux": "^3.5.2", "redux-actions": "0.12.0", @@ -88,7 +89,7 @@ "style-loader": "^0.13.1", "webpack": "1.13.2", "webpack-dashboard": "0.1.8", - "webpack-dev-middleware": "^1.6.1", + "webpack-dev-middleware": "1.8.1", "webpack-hot-middleware": "^2.12.2", "webpack-merge": "^0.14.1" }, diff --git a/server.js b/server.js index 2c2cd57..f65ced6 100644 --- a/server.js +++ b/server.js @@ -36,7 +36,7 @@ const webpackHotMiddlewareConfig = argv.dashboard ? { log: () => {} } : {} app.use(webpackHotMiddleware(compiler, webpackHotMiddlewareConfig)) -const server = app.listen(PORT, 'localhost', err => { +const server = app.listen(PORT, 'localhost', (err) => { if (err) { console.error(err) return @@ -45,7 +45,7 @@ const server = app.listen(PORT, 'localhost', err => { console.log(`Listening at http://localhost:${PORT}`) }) -server.on('error', e => { +server.on('error', (e) => { console.error(e) })