aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-11-07 12:25:00 +0100
committerLibravatar GitHub <noreply@github.com>2017-11-07 12:25:00 +0100
commitc55885e69e195e4413fda3bffffafeaee7c0c9d5 (patch)
treeb13a2894796e45df60c92cf4c999b2974d400091 /src
parentFix lint errors (diff)
parentfix(App): Prevent app from redirecting when dropping link (diff)
downloadferdium-app-c55885e69e195e4413fda3bffffafeaee7c0c9d5.tar.gz
ferdium-app-c55885e69e195e4413fda3bffffafeaee7c0c9d5.tar.zst
ferdium-app-c55885e69e195e4413fda3bffffafeaee7c0c9d5.zip
Merge branch 'develop' into reload-crashed-service
Diffstat (limited to 'src')
-rw-r--r--src/api/server/ServerApi.js17
-rw-r--r--src/app.js4
-rw-r--r--src/components/settings/services/EditServiceForm.js6
-rw-r--r--src/environment.js11
-rw-r--r--src/index.js34
-rw-r--r--src/lib/Menu.js5
-rw-r--r--src/models/Recipe.js19
-rw-r--r--src/models/Service.js7
-rw-r--r--src/stores/AppStore.js69
-rw-r--r--src/webview/notifications.js41
10 files changed, 105 insertions, 108 deletions
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index 8b0b7563c..932b70cdc 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -499,7 +499,7 @@ export default class ServerApi {
499 499
500 return recipe; 500 return recipe;
501 }), 501 }),
502 ).catch(err => console.error(err)); 502 ).catch(err => console.error('Can\'t load recipe', err));
503 } 503 }
504 504
505 _mapRecipePreviewModel(recipes) { 505 _mapRecipePreviewModel(recipes) {
@@ -562,9 +562,16 @@ export default class ServerApi {
562 .filter(file => fs.statSync(path.join(recipesDirectory, file)).isDirectory() && file !== 'temp'); 562 .filter(file => fs.statSync(path.join(recipesDirectory, file)).isDirectory() && file !== 'temp');
563 563
564 const recipes = paths.map((id) => { 564 const recipes = paths.map((id) => {
565 // eslint-disable-next-line 565 let Recipe;
566 const Recipe = require(id)(RecipeModel); 566 try {
567 return new Recipe(loadRecipeConfig(id)); 567 // eslint-disable-next-line
568 Recipe = require(id)(RecipeModel);
569 return new Recipe(loadRecipeConfig(id));
570 } catch (err) {
571 console.error(err);
572 }
573
574 return false;
568 }).filter(recipe => recipe.id).map((data) => { 575 }).filter(recipe => recipe.id).map((data) => {
569 const recipe = data; 576 const recipe = data;
570 577
@@ -579,7 +586,7 @@ export default class ServerApi {
579 586
580 return recipes; 587 return recipes;
581 } catch (err) { 588 } catch (err) {
582 console.debug('Folder `recipe/dev` does not exist'); 589 console.debug('Could not load dev recipes');
583 return false; 590 return false;
584 } 591 }
585 } 592 }
diff --git a/src/app.js b/src/app.js
index b539ea494..a0b88611c 100644
--- a/src/app.js
+++ b/src/app.js
@@ -101,3 +101,7 @@ window.addEventListener('load', () => {
101 }; 101 };
102 window.franz.render(); 102 window.franz.render();
103}); 103});
104
105// Prevent drag and drop into window from redirecting
106window.addEventListener('dragover', event => event.preventDefault());
107window.addEventListener('drop', event => event.preventDefault());
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index fac0f6b9a..9b359a78e 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -69,10 +69,6 @@ const messages = defineMessages({
69export default class EditServiceForm extends Component { 69export default class EditServiceForm extends Component {
70 static propTypes = { 70 static propTypes = {
71 recipe: PropTypes.instanceOf(Recipe).isRequired, 71 recipe: PropTypes.instanceOf(Recipe).isRequired,
72 // service: PropTypes.oneOfType([
73 // PropTypes.object,
74 // PropTypes.instanceOf(Service),
75 // ]),
76 service(props, propName) { 72 service(props, propName) {
77 if (props.action === 'edit' && !(props[propName] instanceof Service)) { 73 if (props.action === 'edit' && !(props[propName] instanceof Service)) {
78 return new Error(`'${propName}'' is expected to be of type 'Service' 74 return new Error(`'${propName}'' is expected to be of type 'Service'
@@ -207,7 +203,7 @@ export default class EditServiceForm extends Component {
207 )} 203 )}
208 {recipe.hasCustomUrl && ( 204 {recipe.hasCustomUrl && (
209 <TabItem title={intl.formatMessage(messages.tabOnPremise)}> 205 <TabItem title={intl.formatMessage(messages.tabOnPremise)}>
210 {user.isPremium ? ( 206 {user.isPremium || recipe.author.find(a => a.email === user.email) ? (
211 <div> 207 <div>
212 <Input field={form.$('customUrl')} /> 208 <Input field={form.$('customUrl')} />
213 {form.error === 'url-validation-error' && ( 209 {form.error === 'url-validation-error' && (
diff --git a/src/environment.js b/src/environment.js
index 7bb2db134..e185120c0 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -8,16 +8,7 @@ export const isMac = process.platform === 'darwin';
8export const isWindows = process.platform === 'win32'; 8export const isWindows = process.platform === 'win32';
9export const isLinux = process.platform === 'linux'; 9export const isLinux = process.platform === 'linux';
10 10
11let ctrlShortcutKey; 11export const ctrlKey = isMac ? '⌘' : 'Ctrl';
12if (isMac) {
13 ctrlShortcutKey = '⌘';
14} else if (isWindows) {
15 ctrlShortcutKey = 'Ctrl';
16} else {
17 ctrlShortcutKey = 'Alt';
18}
19
20export const ctrlKey = ctrlShortcutKey;
21 12
22let api; 13let api;
23if (!isDevMode || (isDevMode && useLiveAPI)) { 14if (!isDevMode || (isDevMode && useLiveAPI)) {
diff --git a/src/index.js b/src/index.js
index 030736fee..f0fe56ae5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,18 +2,14 @@ import { app, BrowserWindow, shell } from 'electron';
2import fs from 'fs-extra'; 2import fs from 'fs-extra';
3import path from 'path'; 3import path from 'path';
4 4
5/* eslint-disable */
6if (require('electron-squirrel-startup')) app.quit();
7
8import windowStateKeeper from 'electron-window-state'; 5import windowStateKeeper from 'electron-window-state';
9 6
10import { isDevMode, isWindows } from './environment'; 7import { isDevMode, isWindows } from './environment';
11import ipcApi from './electron/ipc-api'; 8import ipcApi from './electron/ipc-api';
12import Tray from './lib/Tray'; 9import Tray from './lib/Tray';
13import Settings from './electron/Settings'; 10import Settings from './electron/Settings';
14import { appId } from './package.json'; 11import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
15import './electron/exception'; 12import './electron/exception';
16/* eslint-enable */
17 13
18// Keep a global reference of the window object, if you don't, the window will 14// Keep a global reference of the window object, if you don't, the window will
19// be closed automatically when the JavaScript object is garbage collected. 15// be closed automatically when the JavaScript object is garbage collected.
@@ -22,6 +18,7 @@ let willQuitApp = false;
22 18
23// Ensure that the recipe directory exists 19// Ensure that the recipe directory exists
24fs.ensureDir(path.join(app.getPath('userData'), 'recipes')); 20fs.ensureDir(path.join(app.getPath('userData'), 'recipes'));
21fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp'));
25 22
26// Set App ID for Windows 23// Set App ID for Windows
27if (isWindows) { 24if (isWindows) {
@@ -29,19 +26,18 @@ if (isWindows) {
29} 26}
30 27
31// Force single window 28// Force single window
32if (process.platform !== 'darwin') { 29const isSecondInstance = app.makeSingleInstance(() => {
33 const isSecondInstance = app.makeSingleInstance(() => { 30 if (mainWindow) {
34 if (mainWindow) { 31 if (mainWindow.isMinimized()) mainWindow.restore();
35 if (mainWindow.isMinimized()) mainWindow.restore(); 32 mainWindow.focus();
36 mainWindow.focus();
37 }
38 });
39
40 if (isSecondInstance) {
41 app.quit();
42 } 33 }
34});
35
36if (isSecondInstance) {
37 app.exit();
43} 38}
44 39
40
45// Initialize Settings 41// Initialize Settings
46const settings = new Settings(); 42const settings = new Settings();
47 43
@@ -89,9 +85,13 @@ const createWindow = async () => {
89 // when you should delete the corresponding element. 85 // when you should delete the corresponding element.
90 if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) { 86 if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) {
91 e.preventDefault(); 87 e.preventDefault();
92 mainWindow.hide(); 88 if (isWindows) {
89 mainWindow.minimize();
90 } else {
91 mainWindow.hide();
92 }
93 93
94 if (process.platform === 'win32') { 94 if (isWindows && settings.get('minimizeToSystemTray')) {
95 mainWindow.setSkipTaskbar(true); 95 mainWindow.setSkipTaskbar(true);
96 } 96 }
97 } else { 97 } else {
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index 6bbf302ca..8f0a92c3d 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -1,7 +1,7 @@
1import { remote, shell } from 'electron'; 1import { remote, shell } from 'electron';
2import { autorun, computed, observable, toJS } from 'mobx'; 2import { autorun, computed, observable, toJS } from 'mobx';
3 3
4import { isMac, isLinux } from '../environment'; 4import { isMac } from '../environment';
5 5
6const { app, Menu, dialog } = remote; 6const { app, Menu, dialog } = remote;
7 7
@@ -276,10 +276,9 @@ export default class FranzMenu {
276 const services = this.stores.services.enabled; 276 const services = this.stores.services.enabled;
277 277
278 if (this.stores.user.isLoggedIn) { 278 if (this.stores.user.isLoggedIn) {
279 const systemAcceleratorKey = isLinux ? 'Alt' : 'CmdOrCtrl';
280 return services.map((service, i) => ({ 279 return services.map((service, i) => ({
281 label: service.name, 280 label: service.name,
282 accelerator: i <= 9 ? `${systemAcceleratorKey}+${i + 1}` : null, 281 accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null,
283 type: 'radio', 282 type: 'radio',
284 checked: service.isActive, 283 checked: service.isActive,
285 click: () => { 284 click: () => {
diff --git a/src/models/Recipe.js b/src/models/Recipe.js
index 43a3450b1..9971df77c 100644
--- a/src/models/Recipe.js
+++ b/src/models/Recipe.js
@@ -1,7 +1,8 @@
1import emailParser from 'address-rfc2822';
2
1export default class Recipe { 3export default class Recipe {
2 id = ''; 4 id = '';
3 name = ''; 5 name = '';
4 author = '';
5 description = ''; 6 description = '';
6 version = '1.0'; 7 version = '1.0';
7 path = ''; 8 path = '';
@@ -25,12 +26,13 @@ export default class Recipe {
25 } 26 }
26 27
27 if (!data.id) { 28 if (!data.id) {
28 throw Error('Recipe requires Id'); 29 // Franz 4 recipes do not have an Id
30 throw Error(`Recipe '${data.name}' requires Id`);
29 } 31 }
30 32
31 this.id = data.id || this.id; 33 this.id = data.id || this.id;
32 this.name = data.name || this.name; 34 this.name = data.name || this.name;
33 this.author = data.author || this.author; 35 this.rawAuthor = data.author || this.author;
34 this.description = data.description || this.description; 36 this.description = data.description || this.description;
35 this.version = data.version || this.version; 37 this.version = data.version || this.version;
36 this.path = data.path; 38 this.path = data.path;
@@ -49,4 +51,15 @@ export default class Recipe {
49 51
50 this.message = data.config.message || this.message; 52 this.message = data.config.message || this.message;
51 } 53 }
54
55 get author() {
56 try {
57 const addresses = emailParser.parse(this.rawAuthor);
58 return addresses.map(a => ({ email: a.address, name: a.phrase }));
59 } catch (err) {
60 console.warn(`Not a valid author for ${this.name}`);
61 }
62
63 return [];
64 }
52} 65}
diff --git a/src/models/Service.js b/src/models/Service.js
index 89d6748b5..c7276821a 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -58,7 +58,12 @@ export default class Service {
58 58
59 @computed get url() { 59 @computed get url() {
60 if (this.recipe.hasCustomUrl && this.customUrl) { 60 if (this.recipe.hasCustomUrl && this.customUrl) {
61 let url = normalizeUrl(this.customUrl); 61 let url;
62 try {
63 url = normalizeUrl(this.customUrl);
64 } catch (err) {
65 console.error(`Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`);
66 }
62 67
63 if (typeof this.recipe.buildUrl === 'function') { 68 if (typeof this.recipe.buildUrl === 'function') {
64 url = this.recipe.buildUrl(url); 69 url = this.recipe.buildUrl(url);
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index f608a689e..ecfd621d3 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -2,19 +2,23 @@ import { remote, ipcRenderer, shell } from 'electron';
2import { action, observable } from 'mobx'; 2import { action, observable } from 'mobx';
3import moment from 'moment'; 3import moment from 'moment';
4import key from 'keymaster'; 4import key from 'keymaster';
5import path from 'path'; 5// import path from 'path';
6import idleTimer from '@paulcbetts/system-idle-time'; 6import idleTimer from '@paulcbetts/system-idle-time';
7import AutoLaunch from 'auto-launch';
7 8
8import Store from './lib/Store'; 9import Store from './lib/Store';
9import Request from './lib/Request'; 10import Request from './lib/Request';
10import { CHECK_INTERVAL } from '../config'; 11import { CHECK_INTERVAL } from '../config';
11import { isMac, isLinux } from '../environment'; 12import { isMac } from '../environment';
12import locales from '../i18n/translations'; 13import locales from '../i18n/translations';
13import { gaEvent } from '../lib/analytics'; 14import { gaEvent } from '../lib/analytics';
14import Miner from '../lib/Miner'; 15import Miner from '../lib/Miner';
15 16
16const { app, powerMonitor } = remote; 17const { app, powerMonitor } = remote;
17const defaultLocale = 'en-US'; 18const defaultLocale = 'en-US';
19const autoLauncher = new AutoLaunch({
20 name: 'Franz',
21});
18 22
19export default class AppStore extends Store { 23export default class AppStore extends Store {
20 updateStatusTypes = { 24 updateStatusTypes = {
@@ -41,7 +45,7 @@ export default class AppStore extends Store {
41 miner = null; 45 miner = null;
42 @observable minerHashrate = 0.0; 46 @observable minerHashrate = 0.0;
43 47
44 constructor(...args: any) { 48 constructor(...args) {
45 super(...args); 49 super(...args);
46 50
47 // Register action handlers 51 // Register action handlers
@@ -152,33 +156,24 @@ export default class AppStore extends Store {
152 indicator = '•'; 156 indicator = '•';
153 } else if (unreadDirectMessageCount === 0 && unreadIndirectMessageCount === 0) { 157 } else if (unreadDirectMessageCount === 0 && unreadIndirectMessageCount === 0) {
154 indicator = 0; 158 indicator = 0;
159 } else {
160 indicator = parseInt(indicator, 10);
155 } 161 }
156 162
157 ipcRenderer.send('updateAppIndicator', { indicator }); 163 ipcRenderer.send('updateAppIndicator', { indicator });
158 } 164 }
159 165
160 @action _launchOnStartup({ enable, openInBackground }) { 166 @action _launchOnStartup({ enable }) {
161 this.autoLaunchOnStart = enable; 167 this.autoLaunchOnStart = enable;
162 168
163 let settings = { 169 try {
164 openAtLogin: enable, 170 if (enable) {
165 }; 171 autoLauncher.enable();
166 172 } else {
167 // For Windows 173 autoLauncher.disable();
168 if (process.platform === 'win32') {
169 settings = Object.assign({
170 openAsHidden: openInBackground,
171 path: app.getPath('exe'),
172 args: [
173 '--processStart', `"${path.basename(app.getPath('exe'))}"`,
174 ],
175 }, settings);
176
177 if (openInBackground) {
178 settings.args.push(
179 '--process-start-args', '"--hidden"',
180 );
181 } 174 }
175 } catch (err) {
176 console.warn(err);
182 } 177 }
183 178
184 gaEvent('App', enable ? 'enable autostart' : 'disable autostart'); 179 gaEvent('App', enable ? 'enable autostart' : 'disable autostart');
@@ -287,31 +282,19 @@ export default class AppStore extends Store {
287 } 282 }
288 283
289 async _autoStart() { 284 async _autoStart() {
290 if (!isLinux) { 285 this.autoLaunchOnStart = await this._checkAutoStart();
291 this._checkAutoStart();
292 286
293 // we need to wait until the settings request is resolved 287 // we need to wait until the settings request is resolved
294 await this.stores.settings.allSettingsRequest; 288 await this.stores.settings.allSettingsRequest;
295
296 // We don't set autostart on first launch for macOS as disabling
297 // the option is currently broken
298 // https://github.com/meetfranz/franz/issues/17
299 // https://github.com/electron/electron/issues/10880
300 if (process.platform === 'darwin') return;
301 289
302 if (!this.stores.settings.all.appStarts) { 290 if (!this.stores.settings.all.appStarts) {
303 this.actions.app.launchOnStartup({ 291 this.actions.app.launchOnStartup({
304 enable: true, 292 enable: true,
305 }); 293 });
306 }
307 } 294 }
308 } 295 }
309 296
310 _checkAutoStart() { 297 async _checkAutoStart() {
311 const loginItem = app.getLoginItemSettings({ 298 return autoLauncher.isEnabled() || false;
312 path: app.getPath('exe'),
313 });
314
315 this.autoLaunchOnStart = loginItem.openAtLogin;
316 } 299 }
317} 300}
diff --git a/src/webview/notifications.js b/src/webview/notifications.js
index b3397148d..4055b10de 100644
--- a/src/webview/notifications.js
+++ b/src/webview/notifications.js
@@ -1,47 +1,46 @@
1const { ipcRenderer } = require('electron'); 1const { ipcRenderer } = require('electron');
2const uuidV1 = require('uuid/v1'); 2const uuidV1 = require('uuid/v1');
3// const FranzNotificationStore = [];
4 3
5class Notification { 4class Notification {
5 static permission = 'granted';
6
6 constructor(title = '', options = {}) { 7 constructor(title = '', options = {}) {
7 this.title = title; 8 this.title = title;
8 this.options = options; 9 this.options = options;
9 this.notificationId = uuidV1(); 10 this.notificationId = uuidV1();
10 this.onclick = () => { };
11 11
12 ipcRenderer.sendToHost('notification', Notification.onNotify({ 12 ipcRenderer.sendToHost('notification', this.onNotify({
13 notificationId: this.notificationId, 13 notificationId: this.notificationId,
14 title, 14 title,
15 options, 15 options,
16 })); 16 }));
17 17
18 ipcRenderer.on(`notification-onclick:${this.notificationId}`, () => { 18 ipcRenderer.once(`notification-onclick:${this.notificationId}`, () => {
19 this.onclick(); 19 this.onclick();
20 }); 20 });
21 } 21 }
22}
23 22
24Notification.permission = 'granted'; 23 static requestPermission(cb = null) {
24 if (!cb) {
25 return new Promise((resolve) => {
26 resolve(Notification.permission);
27 });
28 }
25 29
26Notification.requestPermission = (cb = null) => { 30 if (typeof (cb) === 'function') {
27 console.log(this); 31 return cb(Notification.permission);
28 if (!cb) { 32 }
29 return new Promise((resolve) => {
30 resolve(Notification.permission);
31 });
32 }
33 33
34 if (typeof (cb) === 'function') { 34 return Notification.permission;
35 return cb(Notification.permission);
36 } 35 }
37 36
38 return Notification.permission; 37 onNotify(data) {
39}; 38 return data;
39 }
40 40
41Notification.close = () => { 41 onClick() {}
42 // no implementation yet
43};
44 42
45Notification.onNotify = data => data; 43 close() {}
44}
46 45
47window.Notification = Notification; 46window.Notification = Notification;