diff --git a/CHANGELOG.md b/CHANGELOG.md index c608d6c..da8b5cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Crash reporter. + +## [0.4.0] - 2019-04-23 +### Added +- New option: `Check updates at launch`. +- New option: `Launch application minimised`. +- New option: `Minimise application on quit`. +- More logging for debugging. + +### Changed +- Default value for `Update Slack workspaces` set from 5 to 15 minutes. ## [0.3.8] - 2019-04-23 ### Fixed diff --git a/Procfile b/Procfile index 0353ac8..7fc1540 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ react: nodemon -w config-overrides.js --exec yarn react-dev -electron: nodemon -w public/electron.js -w public/utils.js -w public/config.js --exec yarn electron-dev +electron: nodemon -w public/electron.js -w public/lib --exec yarn electron-dev diff --git a/package.json b/package.json index 282189b..cc01d55 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.8", + "version": "0.4.0", "name": "slothy", "description": "Changes your Slack status based on the SSID you're currently connected to.", "productName": "Slothy", @@ -11,7 +11,8 @@ "Pages": "https://kirbo.gitlab.io/slothy/", "Slack": "https://join.slack.com/t/slothy-app/shared_invite/enQtNjE1NTcxMzY1MTU4LTcyMzhhZmMwNzdlMjkwZTQ4NzNkYjc3NWI0NWY5YWVjNDg3NTg5MTlhNGQ2ZGQ4NDZjMGMxM2YxNGQxOTBhOTc", "ClientId": "613360590693.615573455494", - "Protocol": "slothy" + "Protocol": "slothy", + "CrashReportUrl": "" }, "author": "Kirbo (https://gitlab.com/kirbo)", "private": true, diff --git a/public/electron.js b/public/electron.js index 755db7b..622a537 100644 --- a/public/electron.js +++ b/public/electron.js @@ -26,7 +26,10 @@ const { getAppConfigurations, setAppConfigurations, updateStatuses, -} = require('./utils.js'); + // crashReporter, +} = require('./lib/helpers'); + +// crashReporter(); const protocol = packageJson.product.Protocol; @@ -46,7 +49,7 @@ const cached = { slackInstances: null, }; -const setAutoUpdates = async () => { +const setAutoUpdater = async () => { config = await getAppConfigurations(); Object.keys(config.updates).forEach(key => { @@ -57,8 +60,6 @@ const setAutoUpdates = async () => { autoUpdater.logger.transports.file.level = 'info'; } -setAutoUpdates(); - const hideDock = () => { if (app.dock) { app.dock.hide(); @@ -296,38 +297,6 @@ const createWindow = async () => { }); Menu.setApplicationMenu(Menu.buildFromTemplate(MENU_TEMPLATE)); - if (!tray) { - tray = new Tray(nativeImage.createFromPath(iconPath)); - - const contextMenu = Menu.buildFromTemplate([ - { - label: `Open ${packageJson.productName}`, click: () => { - if (mainWindow) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } - mainWindow.show(); - mainWindow.focus(); - showDock(); - } else { - createWindow(); - } - } - }, - { - label: 'Quit', click: () => { - quit = true; - app.quit(); - } - }, - ]); - tray.setToolTip(packageJson.productName); - tray.setContextMenu(contextMenu); - tray.on('click', () => { - mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); - }); - } - mainWindow .once('ready-to-show', () => { mainWindow.show(); @@ -358,7 +327,7 @@ const createWindow = async () => { mainWindow.hide(); }) .on('close', event => { - if (!quit) { + if (config.app.closeToTray && !quit) { event.preventDefault(); } if (mainWindow) { @@ -378,7 +347,39 @@ const createWindow = async () => { }); } +const createTray = async () => { + if (!tray) { + tray = new Tray(nativeImage.createFromPath(iconPath)); + const contextMenu = Menu.buildFromTemplate([ + { + label: `Open ${packageJson.productName}`, click: () => { + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.show(); + mainWindow.focus(); + showDock(); + } else { + createWindow(); + } + } + }, + { + label: 'Quit', click: () => { + quit = true; + app.quit(); + } + }, + ]); + tray.setToolTip(packageJson.productName); + tray.setContextMenu(contextMenu); + tray.on('click', () => { + mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); + }); + } +} if (!app.requestSingleInstanceLock()) { app.quit(); @@ -409,9 +410,17 @@ app mainWindow.focus(); } }) - .on('ready', () => { - createWindow(); - showDock(); + .on('ready', async () => { + createTray(); + config = await getAppConfigurations(); + setAutoUpdater(); + + if (!config.app.launchMinimised) { + createWindow(); + } else { + hideDock(); + } + electron.powerMonitor .on('suspend', () => computerRunning = false) .on('resume', () => computerRunning = true); @@ -424,7 +433,7 @@ app hideDock(); }) .on('will-quit', event => { - if (!quit) { + if (config.app.closeToTray && !quit) { event.preventDefault(); if (mainWindow) { @@ -443,11 +452,14 @@ app ipc .on('initialize', async () => { - ifCachedSend('appConfigurations', getAppConfigurations); + const appConfigurations = await getAppConfigurations(); + ifCachedSend('appConfigurations', () => appConfigurations); ifCachedSend('configurations', getConfigurations); ifCachedSend('connections', getConnections); ifCachedSend('slackInstances', getSlackInstances); - autoUpdater.checkForUpdates(); + if (appConfigurations.updates.checkUpdatesOnLaunch) { + autoUpdater.checkForUpdates(); + } }) .on('getConnections', async (event, data) => sendIfMainWindow('connections', getConnections)) .on('removeSlackInstance', async (event, data) => sendIfMainWindow('slackInstances', removeSlackInstance, data)) @@ -466,13 +478,13 @@ ipc await setStatus(data); sendIfMainWindow('slackInstances', getSlackInstances); }) - .on('saveAppConfigurationsTimers', async (event, data) => { - const appConfigurations = await setAppConfigurations(data); - await startTimers(false); - await sendIfMainWindow('appConfigurations', () => appConfigurations); - }) - .on('saveAppConfigurationsUpdates', async (event, data) => { - const appConfigurations = await setAppConfigurations(data); + .on('saveAppConfigurations', async (event, data) => { + const appConfigurations = await setAppConfigurations(data.appConfigurations); + if (data.property === 'timers') { + await startTimers(false); + } + config = appConfigurations; + setAutoUpdater(); await sendIfMainWindow('appConfigurations', () => appConfigurations); }) .on('checkUpdates', () => autoUpdater.checkForUpdates()) @@ -562,3 +574,16 @@ autoUpdater onConfirm: 'installUpdate', })); }); + +process + .on('unhandledRejection', (reason, p) => { + log.error('Unhandled Rejection at:', p, 'reason:', reason); + }) + .on('uncaughtException', error => { + log.error('uncaughtException', error); + }) + .on('warning', (warning) => { + log.warn(warning.name); + log.warn(warning.message); + log.warn(warning.stack); + }); diff --git a/public/config.js b/public/lib/config.js similarity index 67% rename from public/config.js rename to public/lib/config.js index ae19a17..1c1fbc0 100644 --- a/public/config.js +++ b/public/lib/config.js @@ -1,9 +1,9 @@ module.exports = { // Timers are in seconds timers: { - slackInstances: 300, - connections: 60, updateStatus: 300, + connections: 60, + slackInstances: 900, }, updates: { autoDownload: false, @@ -11,5 +11,11 @@ module.exports = { allowPrerelease: false, allowDowngrade: false, fullChangelog: false, - } + + checkUpdatesOnLaunch: true, + }, + app: { + closeToTray: true, + launchMinimised: false, + }, }; diff --git a/public/utils.js b/public/lib/helpers.js similarity index 95% rename from public/utils.js rename to public/lib/helpers.js index 92245b2..5ebc55e 100644 --- a/public/utils.js +++ b/public/lib/helpers.js @@ -1,3 +1,4 @@ +const electron = require('electron'); const storage = require('electron-json-storage'); const slack = require('slack'); const wifi = require('node-wifi'); @@ -5,9 +6,14 @@ const url = require('url'); const request = require('request'); const log = require('electron-log'); -const packageJson = require('../package.json'); +const packageJson = require('../../package.json'); const appConfigs = require('./config'); +const { + mergeDeep, + recursiveObject, +} = require('./utils'); + const protocol = packageJson.product.Protocol; wifi.init({ @@ -478,10 +484,10 @@ const getAppConfigurations = async () => ( reject(error); } - resolve({ - ...appConfigs, - ...data, - }); + const merged = mergeDeep(appConfigs, data); + const mergedStripped = recursiveObject(appConfigs, merged); + + resolve(mergedStripped); }); }) ) @@ -494,28 +500,28 @@ const setAppConfigurations = async appConfigurations => ( reject(error); } - const recursiveObject = (config, newConfig) => ( - Object.keys(config).reduce((newConfigObject, key) => { - if (typeof config[key] === 'object') { - newConfigObject[key] = recursiveObject(config[key], newConfig[key]); - return newConfigObject; - } - newConfigObject[key] = newConfig[key]; - return newConfigObject; - }, {}) - ); - - storage.set('appConfigs', recursiveObject(appConfigs, appConfigurations), async (error, data) => { + const merged = recursiveObject(appConfigs, appConfigurations); + storage.set('appConfigs', merged, async (error, data) => { if (error) { log.error('setAppConfigurations.appConfigs', error); reject(error); } - resolve(appConfigurations); + resolve(merged); }); }); }) ) + +const crashReporter = async () => { + electron.crashReporter.start({ + companyName: packageJson.author, + productName: packageJson.productName, + submitURL: packageJson.product.CrashReportUrl, + uploadToServer: true, + }) +} + module.exports = { getSlackInstances, updateSlackInstance, @@ -541,4 +547,6 @@ module.exports = { getAppConfigurations, setAppConfigurations, updateStatuses, + + crashReporter, }; diff --git a/public/lib/utils.js b/public/lib/utils.js new file mode 100644 index 0000000..db93593 --- /dev/null +++ b/public/lib/utils.js @@ -0,0 +1,48 @@ +/** + * Simple object check. + * @param item + * @returns {boolean} + */ +const isObject = item => ( + item && typeof item === 'object' && !Array.isArray(item) +) + +/** + * Deep merge two objects. + * @param target + * @param ...sources + */ +const mergeDeep = (target, ...sources) => { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} + +const recursiveObject = (config, newConfig) => ( + Object.keys(config).reduce((newConfigObject, key) => { + if (isObject(config[key])) { + newConfigObject[key] = recursiveObject(config[key], newConfig[key]); + return newConfigObject; + } + newConfigObject[key] = newConfig[key]; + return newConfigObject; + }, {}) +); + +module.exports = { + isObject, + mergeDeep, + recursiveObject, +}; diff --git a/src/assets/css/globalStyles.js b/src/assets/css/globalStyles.js index a7481fd..c6a304e 100644 --- a/src/assets/css/globalStyles.js +++ b/src/assets/css/globalStyles.js @@ -54,8 +54,27 @@ export const GlobalStyles = createGlobalStyle` overflow-y: auto; overflow-x: hidden; word-break: break-word; + + & h2 { + font-size: ${FONT_SIZE['m']}; + font-weight: ${FONT_WEIGHT['thick']}; + border-top: ${BORDER['thin']} solid ${COLOR['borderLight']}; + + &:first-child { + border: none; + } + } + + & h3 { + font-size: ${FONT_SIZE['regular']}; + font-weight: ${FONT_WEIGHT['bold']}; + } } } + + & .ant-progress { + width: calc(100% - ${DIMENSION['2.5x']}); + } } & .ant-notification-notice-btn { display: flex; diff --git a/src/container/App/AppProvider.js b/src/container/App/AppProvider.js index 61f4c5e..6266e33 100644 --- a/src/container/App/AppProvider.js +++ b/src/container/App/AppProvider.js @@ -6,7 +6,7 @@ import INITIAL_STATE from './InitialState'; import { Provider } from './Context'; import Loading from '../Loading'; -import { sortBy, capitalizeFirst } from '../../assets/utils'; +import { sortBy } from '../../assets/utils'; const electron = window.require('electron'); const { ipcRenderer } = electron; @@ -96,7 +96,7 @@ class AppProvider extends Component { ipcRenderer.send('removeConfiguration', { id }); }, updateAppConfigurations: (property, appConfigurations) => { - ipcRenderer.send(`saveAppConfigurations${capitalizeFirst(property)}`, appConfigurations); + ipcRenderer.send('saveAppConfigurations', { property, appConfigurations }); } }; @@ -172,8 +172,14 @@ class AppProvider extends Component { if (data.updates.releaseDate) { message = `${message}