diff options
author | Stefan Malzner <stefan@adlk.io> | 2018-03-04 21:10:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-04 21:10:29 +0100 |
commit | 9af5fd05910615b8101ef84bd1b43d7fe78dedc4 (patch) | |
tree | c768a9682a71b658b4675fe6608b1cc45c5e4e90 | |
parent | fix(App): Fix app getting stuck on load when recipe has an invalid version id... (diff) | |
parent | Finalize titlebar styling (diff) | |
download | ferdium-app-9af5fd05910615b8101ef84bd1b43d7fe78dedc4.tar.gz ferdium-app-9af5fd05910615b8101ef84bd1b43d7fe78dedc4.tar.zst ferdium-app-9af5fd05910615b8101ef84bd1b43d7fe78dedc4.zip |
feat(Windows): Replace window frame with custom menu bar
Title bar for Windows & Linux
-rw-r--r-- | .vscode/launch.json | 11 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/I18n.js | 9 | ||||
-rw-r--r-- | src/components/layout/AppLayout.js | 122 | ||||
-rw-r--r-- | src/environment.js | 12 | ||||
-rw-r--r-- | src/helpers/validation-helpers.js | 35 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 49 | ||||
-rw-r--r-- | src/index.html | 2 | ||||
-rw-r--r-- | src/index.js | 6 | ||||
-rw-r--r-- | src/lib/Menu.js | 600 | ||||
-rw-r--r-- | src/styles/layout.scss | 10 | ||||
-rw-r--r-- | src/styles/main.scss | 2 | ||||
-rw-r--r-- | src/styles/title-bar.scss | 50 | ||||
-rw-r--r-- | yarn.lock | 45 |
14 files changed, 786 insertions, 168 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index a8300f84f..5afc2d051 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json | |||
@@ -9,7 +9,8 @@ | |||
9 | "program": "${workspaceFolder}/build/index.js", | 9 | "program": "${workspaceFolder}/build/index.js", |
10 | "protocol": "inspector", | 10 | "protocol": "inspector", |
11 | "env": { | 11 | "env": { |
12 | "NODE_ENV": "development" | 12 | "NODE_ENV": "development", |
13 | "OS_PLATFORM": "win32" | ||
13 | } | 14 | } |
14 | }, | 15 | }, |
15 | { | 16 | { |
@@ -18,9 +19,10 @@ | |||
18 | "name": "Franz – Live API", | 19 | "name": "Franz – Live API", |
19 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", | 20 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", |
20 | "program": "${workspaceFolder}/build/index.js", | 21 | "program": "${workspaceFolder}/build/index.js", |
21 | "protocol": "inspector", | 22 | "protocol": "inspector", |
22 | "env": { | 23 | "env": { |
23 | "LIVE_API": "1" | 24 | "LIVE_API": "1", |
25 | "OS_PLATFORM": "win32" | ||
24 | } | 26 | } |
25 | }, | 27 | }, |
26 | { | 28 | { |
@@ -31,7 +33,8 @@ | |||
31 | "program": "${workspaceFolder}/build/index.js", | 33 | "program": "${workspaceFolder}/build/index.js", |
32 | "protocol": "inspector", | 34 | "protocol": "inspector", |
33 | "env": { | 35 | "env": { |
34 | "LOCAL_API": "1" | 36 | "LOCAL_API": "1", |
37 | "OS_PLATFORM": "win32" | ||
35 | } | 38 | } |
36 | } | 39 | } |
37 | ] | 40 | ] |
diff --git a/package.json b/package.json index fd96c602b..f35da9880 100644 --- a/package.json +++ b/package.json | |||
@@ -35,6 +35,7 @@ | |||
35 | "classnames": "^2.2.5", | 35 | "classnames": "^2.2.5", |
36 | "du": "^0.1.0", | 36 | "du": "^0.1.0", |
37 | "electron-fetch": "^1.1.0", | 37 | "electron-fetch": "^1.1.0", |
38 | "electron-react-titlebar": "^0.7.1", | ||
38 | "electron-spellchecker": "^1.1.2", | 39 | "electron-spellchecker": "^1.1.2", |
39 | "electron-updater": "^2.4.3", | 40 | "electron-updater": "^2.4.3", |
40 | "electron-window-state": "^4.1.0", | 41 | "electron-window-state": "^4.1.0", |
diff --git a/src/I18n.js b/src/I18n.js index ae3ba2fa9..4ee34157c 100644 --- a/src/I18n.js +++ b/src/I18n.js | |||
@@ -9,11 +9,18 @@ import UserStore from './stores/UserStore'; | |||
9 | 9 | ||
10 | @inject('stores') @observer | 10 | @inject('stores') @observer |
11 | export default class I18N extends Component { | 11 | export default class I18N extends Component { |
12 | componentDidUpdate() { | ||
13 | window.franz.menu.rebuild(); | ||
14 | } | ||
15 | |||
12 | render() { | 16 | render() { |
13 | const { stores, children } = this.props; | 17 | const { stores, children } = this.props; |
14 | const { locale } = stores.app; | 18 | const { locale } = stores.app; |
15 | return ( | 19 | return ( |
16 | <IntlProvider {...{ locale, key: locale, messages: translations[locale] }}> | 20 | <IntlProvider |
21 | {...{ locale, key: locale, messages: translations[locale] }} | ||
22 | ref={(intlProvider) => { window.franz.intl = intlProvider ? intlProvider.getChildContext().intl : null; }} | ||
23 | > | ||
17 | {children} | 24 | {children} |
18 | </IntlProvider> | 25 | </IntlProvider> |
19 | ); | 26 | ); |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 20dc2f764..686476317 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -2,10 +2,13 @@ import React, { Component } from 'react'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import { TitleBar } from 'electron-react-titlebar'; | ||
5 | 6 | ||
6 | import InfoBar from '../ui/InfoBar'; | 7 | import InfoBar from '../ui/InfoBar'; |
7 | import globalMessages from '../../i18n/globalMessages'; | 8 | import globalMessages from '../../i18n/globalMessages'; |
8 | 9 | ||
10 | import { isMac } from '../../environment'; | ||
11 | |||
9 | function createMarkup(HTMLString) { | 12 | function createMarkup(HTMLString) { |
10 | return { __html: HTMLString }; | 13 | return { __html: HTMLString }; |
11 | } | 14 | } |
@@ -87,64 +90,67 @@ export default class AppLayout extends Component { | |||
87 | return ( | 90 | return ( |
88 | <div> | 91 | <div> |
89 | <div className="app"> | 92 | <div className="app"> |
90 | {sidebar} | 93 | {!isMac && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} |
91 | <div className="app__service"> | 94 | <div className="app__content"> |
92 | {news.length > 0 && news.map(item => ( | 95 | {sidebar} |
93 | <InfoBar | 96 | <div className="app__service"> |
94 | key={item.id} | 97 | {news.length > 0 && news.map(item => ( |
95 | position="top" | 98 | <InfoBar |
96 | type={item.type} | 99 | key={item.id} |
97 | sticky={item.sticky} | 100 | position="top" |
98 | onHide={() => removeNewsItem({ newsId: item.id })} | 101 | type={item.type} |
99 | > | 102 | sticky={item.sticky} |
100 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> | 103 | onHide={() => removeNewsItem({ newsId: item.id })} |
101 | </InfoBar> | 104 | > |
102 | ))} | 105 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> |
103 | {!isOnline && ( | 106 | </InfoBar> |
104 | <InfoBar | 107 | ))} |
105 | type="danger" | 108 | {!isOnline && ( |
106 | > | 109 | <InfoBar |
107 | <span className="mdi mdi-flash" /> | 110 | type="danger" |
108 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} | 111 | > |
109 | </InfoBar> | 112 | <span className="mdi mdi-flash" /> |
110 | )} | 113 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} |
111 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( | 114 | </InfoBar> |
112 | <InfoBar | 115 | )} |
113 | type="danger" | 116 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( |
114 | ctaLabel="Try again" | 117 | <InfoBar |
115 | ctaLoading={areRequiredRequestsLoading} | 118 | type="danger" |
116 | sticky | 119 | ctaLabel="Try again" |
117 | onClick={retryRequiredRequests} | 120 | ctaLoading={areRequiredRequestsLoading} |
118 | > | 121 | sticky |
119 | <span className="mdi mdi-flash" /> | 122 | onClick={retryRequiredRequests} |
120 | {intl.formatMessage(messages.requiredRequestsFailed)} | 123 | > |
121 | </InfoBar> | 124 | <span className="mdi mdi-flash" /> |
122 | )} | 125 | {intl.formatMessage(messages.requiredRequestsFailed)} |
123 | {showServicesUpdatedInfoBar && ( | 126 | </InfoBar> |
124 | <InfoBar | 127 | )} |
125 | type="primary" | 128 | {showServicesUpdatedInfoBar && ( |
126 | ctaLabel={intl.formatMessage(messages.buttonReloadServices)} | 129 | <InfoBar |
127 | onClick={reloadServicesAfterUpdate} | 130 | type="primary" |
128 | sticky | 131 | ctaLabel={intl.formatMessage(messages.buttonReloadServices)} |
129 | > | 132 | onClick={reloadServicesAfterUpdate} |
130 | <span className="mdi mdi-power-plug" /> | 133 | sticky |
131 | {intl.formatMessage(messages.servicesUpdated)} | 134 | > |
132 | </InfoBar> | 135 | <span className="mdi mdi-power-plug" /> |
133 | )} | 136 | {intl.formatMessage(messages.servicesUpdated)} |
134 | {appUpdateIsDownloaded && ( | 137 | </InfoBar> |
135 | <InfoBar | 138 | )} |
136 | type="primary" | 139 | {appUpdateIsDownloaded && ( |
137 | ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} | 140 | <InfoBar |
138 | onClick={installAppUpdate} | 141 | type="primary" |
139 | sticky | 142 | ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} |
140 | > | 143 | onClick={installAppUpdate} |
141 | <span className="mdi mdi-information" /> | 144 | sticky |
142 | {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank"> | 145 | > |
143 | <u>{intl.formatMessage(messages.changelog)}</u> | 146 | <span className="mdi mdi-information" /> |
144 | </a> | 147 | {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank"> |
145 | </InfoBar> | 148 | <u>{intl.formatMessage(messages.changelog)}</u> |
146 | )} | 149 | </a> |
147 | {services} | 150 | </InfoBar> |
151 | )} | ||
152 | {services} | ||
153 | </div> | ||
148 | </div> | 154 | </div> |
149 | </div> | 155 | </div> |
150 | {children} | 156 | {children} |
diff --git a/src/environment.js b/src/environment.js index e185120c0..e1762129b 100644 --- a/src/environment.js +++ b/src/environment.js | |||
@@ -4,11 +4,17 @@ export const isDevMode = Boolean(process.execPath.match(/[\\/]electron/)); | |||
4 | export const useLiveAPI = process.env.LIVE_API; | 4 | export const useLiveAPI = process.env.LIVE_API; |
5 | export const useLocalAPI = process.env.LOCAL_API; | 5 | export const useLocalAPI = process.env.LOCAL_API; |
6 | 6 | ||
7 | export const isMac = process.platform === 'darwin'; | 7 | let platform = process.platform; |
8 | export const isWindows = process.platform === 'win32'; | 8 | if (process.env.OS_PLATFORM) { |
9 | export const isLinux = process.platform === 'linux'; | 9 | platform = process.env.OS_PLATFORM; |
10 | } | ||
11 | |||
12 | export const isMac = platform === 'darwin'; | ||
13 | export const isWindows = platform === 'win32'; | ||
14 | export const isLinux = platform === 'linux'; | ||
10 | 15 | ||
11 | export const ctrlKey = isMac ? '⌘' : 'Ctrl'; | 16 | export const ctrlKey = isMac ? '⌘' : 'Ctrl'; |
17 | export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; | ||
12 | 18 | ||
13 | let api; | 19 | let api; |
14 | if (!isDevMode || (isDevMode && useLiveAPI)) { | 20 | if (!isDevMode || (isDevMode && useLiveAPI)) { |
diff --git a/src/helpers/validation-helpers.js b/src/helpers/validation-helpers.js index a8a242d54..2f762437d 100644 --- a/src/helpers/validation-helpers.js +++ b/src/helpers/validation-helpers.js | |||
@@ -1,6 +1,31 @@ | |||
1 | import { defineMessages } from 'react-intl'; | ||
2 | |||
3 | const messages = defineMessages({ | ||
4 | required: { | ||
5 | id: 'validation.required', | ||
6 | defaultMessage: '!!!Field is required', | ||
7 | }, | ||
8 | email: { | ||
9 | id: 'validation.email', | ||
10 | defaultMessage: '!!!Email not valid', | ||
11 | }, | ||
12 | url: { | ||
13 | id: 'validation.url', | ||
14 | defaultMessage: '!!!Not a valid URL', | ||
15 | }, | ||
16 | minLength: { | ||
17 | id: 'validation.minLength', | ||
18 | defaultMessage: '!!!Too few characters', | ||
19 | }, | ||
20 | oneRequired: { | ||
21 | id: 'validation.oneRequired', | ||
22 | defaultMessage: '!!!At least one is required', | ||
23 | }, | ||
24 | }); | ||
25 | |||
1 | export function required({ field }) { | 26 | export function required({ field }) { |
2 | const isValid = (field.value.trim() !== ''); | 27 | const isValid = (field.value.trim() !== ''); |
3 | return [isValid, `${field.label} is required`]; | 28 | return [isValid, window.franz.intl.formatMessage(messages.required, { field: field.label })]; |
4 | } | 29 | } |
5 | 30 | ||
6 | export function email({ field }) { | 31 | export function email({ field }) { |
@@ -13,7 +38,7 @@ export function email({ field }) { | |||
13 | isValid = true; | 38 | isValid = true; |
14 | } | 39 | } |
15 | 40 | ||
16 | return [isValid, `${field.label} not valid`]; | 41 | return [isValid, window.franz.intl.formatMessage(messages.email, { field: field.label })]; |
17 | } | 42 | } |
18 | 43 | ||
19 | export function url({ field }) { | 44 | export function url({ field }) { |
@@ -27,7 +52,7 @@ export function url({ field }) { | |||
27 | isValid = true; | 52 | isValid = true; |
28 | } | 53 | } |
29 | 54 | ||
30 | return [isValid, `${field.label} is not a valid url`]; | 55 | return [isValid, window.franz.intl.formatMessage(messages.url, { field: field.label })]; |
31 | } | 56 | } |
32 | 57 | ||
33 | export function minLength(length) { | 58 | export function minLength(length) { |
@@ -36,13 +61,13 @@ export function minLength(length) { | |||
36 | if (field.touched) { | 61 | if (field.touched) { |
37 | isValid = field.value.length >= length; | 62 | isValid = field.value.length >= length; |
38 | } | 63 | } |
39 | return [isValid, `${field.label} should be at least ${length} characters long.`]; | 64 | return [isValid, window.franz.intl.formatMessage(messages.minLength, { field: field.label, length })]; |
40 | }; | 65 | }; |
41 | } | 66 | } |
42 | 67 | ||
43 | export function oneRequired(targets) { | 68 | export function oneRequired(targets) { |
44 | return ({ field, form }) => { | 69 | return ({ field, form }) => { |
45 | const invalidFields = targets.filter(target => form.$(target).value === ''); | 70 | const invalidFields = targets.filter(target => form.$(target).value === ''); |
46 | return [targets.length !== invalidFields.length, `${field.label} is required`]; | 71 | return [targets.length !== invalidFields.length, window.franz.intl.formatMessage(messages.required, { field: field.label })]; |
47 | }; | 72 | }; |
48 | } | 73 | } |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index d5c0ea441..400a9a5d8 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -199,5 +199,52 @@ | |||
199 | "service.crashHandler.action": "Reload {name}", | 199 | "service.crashHandler.action": "Reload {name}", |
200 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", | 200 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", |
201 | "service.disabledHandler.headline": "{name} is disabled", | 201 | "service.disabledHandler.headline": "{name} is disabled", |
202 | "service.disabledHandler.action": "Enable {name}" | 202 | "service.disabledHandler.action": "Enable {name}", |
203 | "menu.edit": "Edit", | ||
204 | "menu.edit.undo": "Undo", | ||
205 | "menu.edit.redo": "Redo", | ||
206 | "menu.edit.cut": "Cut", | ||
207 | "menu.edit.copy": "Copy", | ||
208 | "menu.edit.paste": "Paste", | ||
209 | "menu.edit.pasteAndMatchStyle": "Paste And Match Style", | ||
210 | "menu.edit.delete": "Delete", | ||
211 | "menu.edit.selectAll": "Select All", | ||
212 | "menu.edit.speech": "Speech", | ||
213 | "menu.edit.startSpeaking": "Start Speaking", | ||
214 | "menu.edit.stopSpeaking": "Stop Speaking", | ||
215 | "menu.edit.startDictation": "Start Dictation", | ||
216 | "menu.edit.emojiSymbols": "Emoji & Symbols", | ||
217 | "menu.view.resetZoom": "Actual Size", | ||
218 | "menu.view.zoomIn": "Zoom In", | ||
219 | "menu.view.zoomOut": "Zoom Out", | ||
220 | "menu.view.enterFullScreen": "Enter Full Screen", | ||
221 | "menu.view.exitFullScreen": "Exit Full Screen", | ||
222 | "menu.view.toggleFullScreen": "Toggle Full Screen", | ||
223 | "menu.view.toggleDevTools": "Toggle Developer Tools", | ||
224 | "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools", | ||
225 | "menu.view.reloadService": "Reload Service", | ||
226 | "menu.view.reloadFranz": "Reload Franz", | ||
227 | "menu.window.minimize": "Minimize", | ||
228 | "menu.window.close": "Close", | ||
229 | "menu.help.learnMore": "Learn More", | ||
230 | "menu.help.changelog": "Changelog", | ||
231 | "menu.help.support": "Support", | ||
232 | "menu.help.tos": "Terms of Service", | ||
233 | "menu.help.privacy": "Privacy Statement", | ||
234 | "menu.file": "File", | ||
235 | "menu.view": "View", | ||
236 | "menu.services": "Services", | ||
237 | "menu.window": "Window", | ||
238 | "menu.help": "Help", | ||
239 | "menu.app.about": "About Franz", | ||
240 | "menu.app.settings": "Settings", | ||
241 | "menu.app.hide": "Hide", | ||
242 | "menu.app.hideOthers": "Hide Others", | ||
243 | "menu.app.unhide": "Unhide", | ||
244 | "menu.app.quit": "Quit", | ||
245 | "menu.services.addNewService": "Add New Service...", | ||
246 | "validation.required": "{field} is required", | ||
247 | "validation.email": "{field} is not valid", | ||
248 | "validation.url": "{field} is not a valid URL", | ||
249 | "validation.minLength": "{field} should be at least {length} characters long" | ||
203 | } | 250 | } |
diff --git a/src/index.html b/src/index.html index 9e5acd705..6c259e5be 100644 --- a/src/index.html +++ b/src/index.html | |||
@@ -11,7 +11,7 @@ | |||
11 | <div class="dev-warning">DEV MODE</div> | 11 | <div class="dev-warning">DEV MODE</div> |
12 | <div id="root"></div> | 12 | <div id="root"></div> |
13 | <script> | 13 | <script> |
14 | document.querySelector('body').classList.add(process.platform); | 14 | document.querySelector('body').classList.add(process.env.OS_PLATFORM ? process.env.OS_PLATFORM : process.platform); |
15 | 15 | ||
16 | const { isDevMode } = require('./environment'); | 16 | const { isDevMode } = require('./environment'); |
17 | if (isDevMode) { | 17 | if (isDevMode) { |
diff --git a/src/index.js b/src/index.js index f82bb3590..bb09bf42c 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -4,7 +4,7 @@ import path from 'path'; | |||
4 | 4 | ||
5 | import windowStateKeeper from 'electron-window-state'; | 5 | import windowStateKeeper from 'electron-window-state'; |
6 | 6 | ||
7 | import { isDevMode, isWindows } from './environment'; | 7 | import { isDevMode, isMac, isWindows } from './environment'; |
8 | import ipcApi from './electron/ipc-api'; | 8 | import ipcApi from './electron/ipc-api'; |
9 | import Tray from './lib/Tray'; | 9 | import Tray from './lib/Tray'; |
10 | import Settings from './electron/Settings'; | 10 | import Settings from './electron/Settings'; |
@@ -72,9 +72,9 @@ const createWindow = () => { | |||
72 | height: mainWindowState.height, | 72 | height: mainWindowState.height, |
73 | minWidth: 600, | 73 | minWidth: 600, |
74 | minHeight: 500, | 74 | minHeight: 500, |
75 | titleBarStyle: 'hidden', | 75 | titleBarStyle: isMac ? 'hidden' : '', |
76 | frame: false, | ||
76 | backgroundColor: '#3498db', | 77 | backgroundColor: '#3498db', |
77 | autoHideMenuBar: true, | ||
78 | }); | 78 | }); |
79 | 79 | ||
80 | // Initialize System Tray | 80 | // Initialize System Tray |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 16e91374e..5a05e47b3 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -1,113 +1,477 @@ | |||
1 | import { remote, shell } from 'electron'; | 1 | import { remote, shell } from 'electron'; |
2 | import { autorun, computed, observable, toJS } from 'mobx'; | 2 | import { observable, autorun, computed } from 'mobx'; |
3 | import { defineMessages } from 'react-intl'; | ||
3 | 4 | ||
4 | import { isMac } from '../environment'; | 5 | import { isMac, ctrlKey, cmdKey } from '../environment'; |
5 | 6 | ||
6 | const { app, Menu, dialog } = remote; | 7 | const { app, Menu, dialog } = remote; |
7 | 8 | ||
8 | const template = [ | 9 | const menuItems = defineMessages({ |
10 | edit: { | ||
11 | id: 'menu.edit', | ||
12 | defaultMessage: '!!!Edit', | ||
13 | }, | ||
14 | undo: { | ||
15 | id: 'menu.edit.undo', | ||
16 | defaultMessage: '!!!Undo', | ||
17 | }, | ||
18 | redo: { | ||
19 | id: 'menu.edit.redo', | ||
20 | defaultMessage: '!!!Redo', | ||
21 | }, | ||
22 | cut: { | ||
23 | id: 'menu.edit.cut', | ||
24 | defaultMessage: '!!!Cut', | ||
25 | }, | ||
26 | copy: { | ||
27 | id: 'menu.edit.copy', | ||
28 | defaultMessage: '!!!Copy', | ||
29 | }, | ||
30 | paste: { | ||
31 | id: 'menu.edit.paste', | ||
32 | defaultMessage: '!!!Paste', | ||
33 | }, | ||
34 | pasteAndMatchStyle: { | ||
35 | id: 'menu.edit.pasteAndMatchStyle', | ||
36 | defaultMessage: '!!!Paste And Match Style', | ||
37 | }, | ||
38 | delete: { | ||
39 | id: 'menu.edit.delete', | ||
40 | defaultMessage: '!!!Delete', | ||
41 | }, | ||
42 | selectAll: { | ||
43 | id: 'menu.edit.selectAll', | ||
44 | defaultMessage: '!!!Select All', | ||
45 | }, | ||
46 | speech: { | ||
47 | id: 'menu.edit.speech', | ||
48 | defaultMessage: '!!!Speech', | ||
49 | }, | ||
50 | startSpeaking: { | ||
51 | id: 'menu.edit.startSpeaking', | ||
52 | defaultMessage: '!!!Start Speaking', | ||
53 | }, | ||
54 | stopSpeaking: { | ||
55 | id: 'menu.edit.stopSpeaking', | ||
56 | defaultMessage: '!!!Stop Speaking', | ||
57 | }, | ||
58 | startDictation: { | ||
59 | id: 'menu.edit.startDictation', | ||
60 | defaultMessage: '!!!Start Dictation', | ||
61 | }, | ||
62 | emojiSymbols: { | ||
63 | id: 'menu.edit.emojiSymbols', | ||
64 | defaultMessage: '!!!Emoji & Symbols', | ||
65 | }, | ||
66 | resetZoom: { | ||
67 | id: 'menu.view.resetZoom', | ||
68 | defaultMessage: '!!!Actual Size', | ||
69 | }, | ||
70 | zoomIn: { | ||
71 | id: 'menu.view.zoomIn', | ||
72 | defaultMessage: '!!!Zoom In', | ||
73 | }, | ||
74 | zoomOut: { | ||
75 | id: 'menu.view.zoomOut', | ||
76 | defaultMessage: '!!!Zoom Out', | ||
77 | }, | ||
78 | enterFullScreen: { | ||
79 | id: 'menu.view.enterFullScreen', | ||
80 | defaultMessage: '!!!Enter Full Screen', | ||
81 | }, | ||
82 | exitFullScreen: { | ||
83 | id: 'menu.view.exitFullScreen', | ||
84 | defaultMessage: '!!!Exit Full Screen', | ||
85 | }, | ||
86 | toggleFullScreen: { | ||
87 | id: 'menu.view.toggleFullScreen', | ||
88 | defaultMessage: '!!!Toggle Full Screen', | ||
89 | }, | ||
90 | toggleDevTools: { | ||
91 | id: 'menu.view.toggleDevTools', | ||
92 | defaultMessage: '!!!Toggle Developer Tools', | ||
93 | }, | ||
94 | toggleServiceDevTools: { | ||
95 | id: 'menu.view.toggleServiceDevTools', | ||
96 | defaultMessage: '!!!Toggle Service Developer Tools', | ||
97 | }, | ||
98 | reloadService: { | ||
99 | id: 'menu.view.reloadService', | ||
100 | defaultMessage: '!!!Reload Service', | ||
101 | }, | ||
102 | reloadFranz: { | ||
103 | id: 'menu.view.reloadFranz', | ||
104 | defaultMessage: '!!!Reload Franz', | ||
105 | }, | ||
106 | minimize: { | ||
107 | id: 'menu.window.minimize', | ||
108 | defaultMessage: '!!!Minimize', | ||
109 | }, | ||
110 | close: { | ||
111 | id: 'menu.window.close', | ||
112 | defaultMessage: '!!!Close', | ||
113 | }, | ||
114 | learnMore: { | ||
115 | id: 'menu.help.learnMore', | ||
116 | defaultMessage: '!!!Learn More', | ||
117 | }, | ||
118 | changelog: { | ||
119 | id: 'menu.help.changelog', | ||
120 | defaultMessage: '!!!Changelog', | ||
121 | }, | ||
122 | support: { | ||
123 | id: 'menu.help.support', | ||
124 | defaultMessage: '!!!Support', | ||
125 | }, | ||
126 | tos: { | ||
127 | id: 'menu.help.tos', | ||
128 | defaultMessage: '!!!Terms of Service', | ||
129 | }, | ||
130 | privacy: { | ||
131 | id: 'menu.help.privacy', | ||
132 | defaultMessage: '!!!Privacy Statement', | ||
133 | }, | ||
134 | file: { | ||
135 | id: 'menu.file', | ||
136 | defaultMessage: '!!!File', | ||
137 | }, | ||
138 | view: { | ||
139 | id: 'menu.view', | ||
140 | defaultMessage: '!!!View', | ||
141 | }, | ||
142 | services: { | ||
143 | id: 'menu.services', | ||
144 | defaultMessage: '!!!Services', | ||
145 | }, | ||
146 | window: { | ||
147 | id: 'menu.window', | ||
148 | defaultMessage: '!!!Window', | ||
149 | }, | ||
150 | help: { | ||
151 | id: 'menu.help', | ||
152 | defaultMessage: '!!!Help', | ||
153 | }, | ||
154 | about: { | ||
155 | id: 'menu.app.about', | ||
156 | defaultMessage: '!!!About Franz', | ||
157 | }, | ||
158 | settings: { | ||
159 | id: 'menu.app.settings', | ||
160 | defaultMessage: '!!!Settings', | ||
161 | }, | ||
162 | hide: { | ||
163 | id: 'menu.app.hide', | ||
164 | defaultMessage: '!!!Hide', | ||
165 | }, | ||
166 | hideOthers: { | ||
167 | id: 'menu.app.hideOthers', | ||
168 | defaultMessage: '!!!Hide Others', | ||
169 | }, | ||
170 | unhide: { | ||
171 | id: 'menu.app.unhide', | ||
172 | defaultMessage: '!!!Unhide', | ||
173 | }, | ||
174 | quit: { | ||
175 | id: 'menu.app.quit', | ||
176 | defaultMessage: '!!!Quit', | ||
177 | }, | ||
178 | addNewService: { | ||
179 | id: 'menu.services.addNewService', | ||
180 | defaultMessage: '!!!Add New Service...', | ||
181 | }, | ||
182 | }); | ||
183 | |||
184 | function getActiveWebview() { | ||
185 | return window.franz.stores.services.active.webview; | ||
186 | } | ||
187 | |||
188 | const _templateFactory = intl => [ | ||
9 | { | 189 | { |
10 | label: 'Edit', | 190 | label: intl.formatMessage(menuItems.edit), |
11 | submenu: [ | 191 | submenu: [ |
12 | { | 192 | { |
193 | label: intl.formatMessage(menuItems.undo), | ||
13 | role: 'undo', | 194 | role: 'undo', |
14 | }, | 195 | }, |
15 | { | 196 | { |
197 | label: intl.formatMessage(menuItems.redo), | ||
16 | role: 'redo', | 198 | role: 'redo', |
17 | }, | 199 | }, |
18 | { | 200 | { |
19 | type: 'separator', | 201 | type: 'separator', |
20 | }, | 202 | }, |
21 | { | 203 | { |
22 | role: 'cut', | 204 | label: intl.formatMessage(menuItems.cut), |
205 | accelerator: 'Cmd+X', | ||
206 | selector: 'cut:', | ||
23 | }, | 207 | }, |
24 | { | 208 | { |
25 | label: 'Copy', | 209 | label: intl.formatMessage(menuItems.copy), |
26 | accelerator: 'CmdOrCtrl+C', | 210 | accelerator: 'Cmd+C', |
27 | selector: 'copy:', | 211 | selector: 'copy:', |
28 | }, | 212 | }, |
29 | { | 213 | { |
30 | label: 'Paste', | 214 | label: intl.formatMessage(menuItems.paste), |
31 | accelerator: 'CmdOrCtrl+V', | 215 | accelerator: 'Cmd+V', |
32 | selector: 'paste:', | 216 | selector: 'paste:', |
33 | }, | 217 | }, |
34 | { | 218 | { |
35 | role: 'pasteandmatchstyle', | 219 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), |
220 | accelerator: 'Cmd+Shift+V', | ||
221 | selector: 'pasteAndMatchStyle:', | ||
36 | }, | 222 | }, |
37 | { | 223 | { |
224 | label: intl.formatMessage(menuItems.delete), | ||
38 | role: 'delete', | 225 | role: 'delete', |
39 | }, | 226 | }, |
40 | { | 227 | { |
41 | role: 'selectall', | 228 | label: intl.formatMessage(menuItems.selectAll), |
229 | accelerator: 'Cmd+A', | ||
230 | selector: 'selectAll:', | ||
42 | }, | 231 | }, |
43 | ], | 232 | ], |
44 | }, | 233 | }, |
45 | { | 234 | { |
46 | label: 'View', | 235 | label: intl.formatMessage(menuItems.view), |
47 | submenu: [ | 236 | submenu: [ |
48 | { | 237 | { |
49 | type: 'separator', | 238 | type: 'separator', |
50 | }, | 239 | }, |
51 | { | 240 | { |
241 | label: intl.formatMessage(menuItems.resetZoom), | ||
52 | role: 'resetzoom', | 242 | role: 'resetzoom', |
53 | }, | 243 | }, |
54 | { | 244 | { |
245 | label: intl.formatMessage(menuItems.zoomIn), | ||
246 | // accelerator: 'Cmd+=', | ||
55 | role: 'zoomin', | 247 | role: 'zoomin', |
56 | accelerator: 'CommandOrControl+=', | ||
57 | }, | 248 | }, |
58 | { | 249 | { |
250 | label: intl.formatMessage(menuItems.zoomOut), | ||
59 | role: 'zoomout', | 251 | role: 'zoomout', |
60 | }, | 252 | }, |
61 | { | 253 | { |
62 | type: 'separator', | 254 | type: 'separator', |
63 | }, | 255 | }, |
64 | { | 256 | { |
257 | label: app.mainWindow.isFullScreen() // label doesn't work, gets overridden by Electron | ||
258 | ? intl.formatMessage(menuItems.exitFullScreen) | ||
259 | : intl.formatMessage(menuItems.enterFullScreen), | ||
65 | role: 'togglefullscreen', | 260 | role: 'togglefullscreen', |
66 | }, | 261 | }, |
67 | ], | 262 | ], |
68 | }, | 263 | }, |
69 | { | 264 | { |
70 | label: 'Services', | 265 | label: intl.formatMessage(menuItems.services), |
71 | submenu: [], | 266 | submenu: [], |
72 | }, | 267 | }, |
73 | { | 268 | { |
269 | label: intl.formatMessage(menuItems.window), | ||
74 | role: 'window', | 270 | role: 'window', |
75 | submenu: [ | 271 | submenu: [ |
76 | { | 272 | { |
273 | label: intl.formatMessage(menuItems.minimize), | ||
77 | role: 'minimize', | 274 | role: 'minimize', |
78 | }, | 275 | }, |
79 | { | 276 | { |
277 | label: intl.formatMessage(menuItems.close), | ||
80 | role: 'close', | 278 | role: 'close', |
81 | }, | 279 | }, |
82 | ], | 280 | ], |
83 | }, | 281 | }, |
84 | { | 282 | { |
283 | label: intl.formatMessage(menuItems.help), | ||
85 | role: 'help', | 284 | role: 'help', |
86 | submenu: [ | 285 | submenu: [ |
87 | { | 286 | { |
88 | label: 'Learn More', | 287 | label: intl.formatMessage(menuItems.learnMore), |
89 | click() { shell.openExternal('http://meetfranz.com'); }, | 288 | click() { shell.openExternal('http://meetfranz.com'); }, |
90 | }, | 289 | }, |
91 | { | 290 | { |
92 | label: 'Changelog', | 291 | label: intl.formatMessage(menuItems.changelog), |
93 | click() { shell.openExternal('https://github.com/meetfranz/franz/blob/master/CHANGELOG.md'); }, | 292 | click() { shell.openExternal('https://github.com/meetfranz/franz/blob/master/CHANGELOG.md'); }, |
94 | }, | 293 | }, |
95 | { | 294 | { |
96 | type: 'separator', | 295 | type: 'separator', |
97 | }, | 296 | }, |
98 | { | 297 | { |
99 | label: 'Support', | 298 | label: intl.formatMessage(menuItems.support), |
100 | click() { shell.openExternal('http://meetfranz.com/support'); }, | 299 | click() { shell.openExternal('http://meetfranz.com/support'); }, |
101 | }, | 300 | }, |
102 | { | 301 | { |
103 | type: 'separator', | 302 | type: 'separator', |
104 | }, | 303 | }, |
105 | { | 304 | { |
106 | label: 'Terms of Service', | 305 | label: intl.formatMessage(menuItems.tos), |
107 | click() { shell.openExternal('https://meetfranz.com/terms'); }, | 306 | click() { shell.openExternal('https://meetfranz.com/terms'); }, |
108 | }, | 307 | }, |
109 | { | 308 | { |
110 | label: 'Privacy Statement', | 309 | label: intl.formatMessage(menuItems.privacy), |
310 | click() { shell.openExternal('https://meetfranz.com/privacy'); }, | ||
311 | }, | ||
312 | ], | ||
313 | }, | ||
314 | ]; | ||
315 | |||
316 | const _titleBarTemplateFactory = intl => [ | ||
317 | { | ||
318 | label: intl.formatMessage(menuItems.edit), | ||
319 | submenu: [ | ||
320 | { | ||
321 | label: intl.formatMessage(menuItems.undo), | ||
322 | accelerator: `${ctrlKey}+Z`, | ||
323 | click() { | ||
324 | getActiveWebview().undo(); | ||
325 | }, | ||
326 | }, | ||
327 | { | ||
328 | label: intl.formatMessage(menuItems.redo), | ||
329 | accelerator: `${ctrlKey}+Y`, | ||
330 | click() { | ||
331 | getActiveWebview().redo(); | ||
332 | }, | ||
333 | }, | ||
334 | { | ||
335 | type: 'separator', | ||
336 | }, | ||
337 | { | ||
338 | label: intl.formatMessage(menuItems.cut), | ||
339 | accelerator: `${ctrlKey}+X`, | ||
340 | click() { | ||
341 | getActiveWebview().cut(); | ||
342 | }, | ||
343 | }, | ||
344 | { | ||
345 | label: intl.formatMessage(menuItems.copy), | ||
346 | accelerator: `${ctrlKey}+C`, | ||
347 | click() { | ||
348 | getActiveWebview().copy(); | ||
349 | }, | ||
350 | }, | ||
351 | { | ||
352 | label: intl.formatMessage(menuItems.paste), | ||
353 | accelerator: `${ctrlKey}+V`, | ||
354 | click() { | ||
355 | getActiveWebview().paste(); | ||
356 | }, | ||
357 | }, | ||
358 | { | ||
359 | label: intl.formatMessage(menuItems.pasteAndMatchStyle), | ||
360 | accelerator: `${ctrlKey}+Shift+V`, | ||
361 | click() { | ||
362 | getActiveWebview().pasteAndMatchStyle(); | ||
363 | }, | ||
364 | }, | ||
365 | { | ||
366 | label: intl.formatMessage(menuItems.delete), | ||
367 | click() { | ||
368 | getActiveWebview().delete(); | ||
369 | }, | ||
370 | }, | ||
371 | { | ||
372 | label: intl.formatMessage(menuItems.selectAll), | ||
373 | accelerator: `${ctrlKey}+A`, | ||
374 | click() { | ||
375 | getActiveWebview().selectAll(); | ||
376 | }, | ||
377 | }, | ||
378 | ], | ||
379 | }, | ||
380 | { | ||
381 | label: intl.formatMessage(menuItems.view), | ||
382 | submenu: [ | ||
383 | { | ||
384 | type: 'separator', | ||
385 | }, | ||
386 | { | ||
387 | label: intl.formatMessage(menuItems.resetZoom), | ||
388 | accelerator: `${ctrlKey}+0`, | ||
389 | click() { | ||
390 | getActiveWebview().setZoomLevel(0); | ||
391 | }, | ||
392 | }, | ||
393 | { | ||
394 | label: intl.formatMessage(menuItems.zoomIn), | ||
395 | accelerator: `${ctrlKey}+Plus`, | ||
396 | click() { | ||
397 | getActiveWebview().getZoomLevel((zoomLevel) => { | ||
398 | getActiveWebview().setZoomLevel(zoomLevel === 5 ? zoomLevel : zoomLevel + 1); | ||
399 | }); | ||
400 | }, | ||
401 | }, | ||
402 | { | ||
403 | label: intl.formatMessage(menuItems.zoomOut), | ||
404 | accelerator: `${ctrlKey}+-`, | ||
405 | click() { | ||
406 | getActiveWebview().getZoomLevel((zoomLevel) => { | ||
407 | getActiveWebview().setZoomLevel(zoomLevel === -5 ? zoomLevel : zoomLevel - 1); | ||
408 | }); | ||
409 | }, | ||
410 | }, | ||
411 | { | ||
412 | type: 'separator', | ||
413 | }, | ||
414 | { | ||
415 | label: app.mainWindow.isFullScreen() // label doesn't work, gets overridden by Electron | ||
416 | ? intl.formatMessage(menuItems.exitFullScreen) | ||
417 | : intl.formatMessage(menuItems.enterFullScreen), | ||
418 | accelerator: `${ctrlKey}+F`, | ||
419 | click(menuItem, browserWindow) { | ||
420 | browserWindow.setFullScreen(!browserWindow.isFullScreen()); | ||
421 | }, | ||
422 | }, | ||
423 | ], | ||
424 | }, | ||
425 | { | ||
426 | label: intl.formatMessage(menuItems.services), | ||
427 | submenu: [], | ||
428 | }, | ||
429 | { | ||
430 | label: intl.formatMessage(menuItems.window), | ||
431 | submenu: [ | ||
432 | { | ||
433 | label: intl.formatMessage(menuItems.minimize), | ||
434 | accelerator: 'Alt+M', | ||
435 | click(menuItem, browserWindow) { | ||
436 | browserWindow.minimize(); | ||
437 | }, | ||
438 | }, | ||
439 | { | ||
440 | label: intl.formatMessage(menuItems.close), | ||
441 | accelerator: 'Alt+W', | ||
442 | click(menuItem, browserWindow) { | ||
443 | browserWindow.close(); | ||
444 | }, | ||
445 | }, | ||
446 | ], | ||
447 | }, | ||
448 | { | ||
449 | label: '?', | ||
450 | submenu: [ | ||
451 | { | ||
452 | label: intl.formatMessage(menuItems.learnMore), | ||
453 | click() { shell.openExternal('http://meetfranz.com'); }, | ||
454 | }, | ||
455 | { | ||
456 | label: intl.formatMessage(menuItems.changelog), | ||
457 | click() { shell.openExternal('https://github.com/meetfranz/franz/blob/master/CHANGELOG.md'); }, | ||
458 | }, | ||
459 | { | ||
460 | type: 'separator', | ||
461 | }, | ||
462 | { | ||
463 | label: intl.formatMessage(menuItems.support), | ||
464 | click() { shell.openExternal('http://meetfranz.com/support'); }, | ||
465 | }, | ||
466 | { | ||
467 | type: 'separator', | ||
468 | }, | ||
469 | { | ||
470 | label: intl.formatMessage(menuItems.tos), | ||
471 | click() { shell.openExternal('https://meetfranz.com/terms'); }, | ||
472 | }, | ||
473 | { | ||
474 | label: intl.formatMessage(menuItems.privacy), | ||
111 | click() { shell.openExternal('https://meetfranz.com/privacy'); }, | 475 | click() { shell.openExternal('https://meetfranz.com/privacy'); }, |
112 | }, | 476 | }, |
113 | ], | 477 | ], |
@@ -115,7 +479,7 @@ const template = [ | |||
115 | ]; | 479 | ]; |
116 | 480 | ||
117 | export default class FranzMenu { | 481 | export default class FranzMenu { |
118 | @observable tpl = template; | 482 | @observable currentTemplate = []; |
119 | 483 | ||
120 | constructor(stores, actions) { | 484 | constructor(stores, actions) { |
121 | this.stores = stores; | 485 | this.stores = stores; |
@@ -124,23 +488,45 @@ export default class FranzMenu { | |||
124 | autorun(this._build.bind(this)); | 488 | autorun(this._build.bind(this)); |
125 | } | 489 | } |
126 | 490 | ||
491 | rebuild() { | ||
492 | this._build(); | ||
493 | } | ||
494 | |||
495 | get template() { | ||
496 | return this.currentTemplate.toJS(); | ||
497 | } | ||
498 | |||
127 | _build() { | 499 | _build() { |
128 | const tpl = toJS(this.tpl); | 500 | // console.log(window.franz); |
501 | const serviceTpl = Object.assign([], this.serviceTpl); // need to clone object so we don't modify computed (cached) object | ||
502 | |||
503 | if (window.franz === undefined) { | ||
504 | return; | ||
505 | } | ||
506 | |||
507 | const intl = window.franz.intl; | ||
508 | const tpl = isMac ? _templateFactory(intl) : _titleBarTemplateFactory(intl); | ||
129 | 509 | ||
130 | tpl[1].submenu.push({ | 510 | tpl[1].submenu.push({ |
131 | role: 'toggledevtools', | 511 | type: 'separator', |
512 | }, { | ||
513 | label: intl.formatMessage(menuItems.toggleDevTools), | ||
514 | accelerator: `${cmdKey}+Alt+I`, | ||
515 | click: (menuItem, browserWindow) => { | ||
516 | browserWindow.webContents.toggleDevTools(); | ||
517 | }, | ||
132 | }, { | 518 | }, { |
133 | label: 'Toggle Service Developer Tools', | 519 | label: intl.formatMessage(menuItems.toggleServiceDevTools), |
134 | accelerator: 'CmdOrCtrl+Shift+Alt+i', | 520 | accelerator: `${cmdKey}+Shift+Alt+I`, |
135 | click: () => { | 521 | click: () => { |
136 | this.actions.service.openDevToolsForActiveService(); | 522 | this.actions.service.openDevToolsForActiveService(); |
137 | }, | 523 | }, |
138 | }); | 524 | }); |
139 | 525 | ||
140 | tpl[1].submenu.unshift({ | 526 | tpl[1].submenu.unshift({ |
141 | label: 'Reload Service', | 527 | label: intl.formatMessage(menuItems.reloadService), |
142 | id: 'reloadService', | 528 | id: 'reloadService', // TODO: needed? |
143 | accelerator: 'CmdOrCtrl+R', | 529 | accelerator: `${cmdKey}+R`, |
144 | click: () => { | 530 | click: () => { |
145 | if (this.stores.user.isLoggedIn | 531 | if (this.stores.user.isLoggedIn |
146 | && this.stores.services.enabled.length > 0) { | 532 | && this.stores.services.enabled.length > 0) { |
@@ -150,93 +536,128 @@ export default class FranzMenu { | |||
150 | } | 536 | } |
151 | }, | 537 | }, |
152 | }, { | 538 | }, { |
153 | label: 'Reload Franz', | 539 | label: intl.formatMessage(menuItems.reloadFranz), |
154 | accelerator: 'CmdOrCtrl+Shift+R', | 540 | accelerator: `${cmdKey}+Shift+R`, |
155 | click: () => { | 541 | click: () => { |
156 | window.location.reload(); | 542 | window.location.reload(); |
157 | }, | 543 | }, |
158 | }); | 544 | }); |
159 | 545 | ||
160 | if (isMac) { | 546 | tpl.unshift({ |
161 | tpl.unshift({ | 547 | label: isMac ? app.getName() : intl.formatMessage(menuItems.file), |
162 | label: app.getName(), | 548 | submenu: [ |
163 | submenu: [ | 549 | { |
164 | { | 550 | label: intl.formatMessage(menuItems.about), |
165 | role: 'about', | 551 | role: 'about', |
166 | }, | 552 | }, |
167 | { | 553 | { |
168 | type: 'separator', | 554 | type: 'separator', |
169 | }, | 555 | }, |
170 | { | 556 | { |
171 | label: 'Settings', | 557 | label: intl.formatMessage(menuItems.settings), |
172 | accelerator: 'CmdOrCtrl+,', | 558 | accelerator: 'CmdOrCtrl+,', |
173 | click: () => { | 559 | click: () => { |
174 | this.actions.ui.openSettings({ path: 'app' }); | 560 | this.actions.ui.openSettings({ path: 'app' }); |
175 | }, | ||
176 | }, | ||
177 | { | ||
178 | type: 'separator', | ||
179 | }, | ||
180 | { | ||
181 | role: 'services', | ||
182 | submenu: [], | ||
183 | }, | ||
184 | { | ||
185 | type: 'separator', | ||
186 | }, | ||
187 | { | ||
188 | role: 'hide', | ||
189 | }, | ||
190 | { | ||
191 | role: 'hideothers', | ||
192 | }, | ||
193 | { | ||
194 | role: 'unhide', | ||
195 | }, | ||
196 | { | ||
197 | type: 'separator', | ||
198 | }, | ||
199 | { | ||
200 | role: 'quit', | ||
201 | }, | 561 | }, |
202 | ], | 562 | }, |
203 | }); | 563 | { |
564 | type: 'separator', | ||
565 | }, | ||
566 | { | ||
567 | label: intl.formatMessage(menuItems.services), | ||
568 | role: 'services', | ||
569 | submenu: [], | ||
570 | }, | ||
571 | { | ||
572 | type: 'separator', | ||
573 | }, | ||
574 | { | ||
575 | label: intl.formatMessage(menuItems.hide), | ||
576 | role: 'hide', | ||
577 | }, | ||
578 | { | ||
579 | label: intl.formatMessage(menuItems.hideOthers), | ||
580 | role: 'hideothers', | ||
581 | }, | ||
582 | { | ||
583 | label: intl.formatMessage(menuItems.unhide), | ||
584 | role: 'unhide', | ||
585 | }, | ||
586 | { | ||
587 | type: 'separator', | ||
588 | }, | ||
589 | { | ||
590 | label: intl.formatMessage(menuItems.quit), | ||
591 | role: 'quit', | ||
592 | }, | ||
593 | ], | ||
594 | }); | ||
595 | |||
596 | const about = { | ||
597 | label: intl.formatMessage(menuItems.about), | ||
598 | click: () => { | ||
599 | dialog.showMessageBox({ | ||
600 | type: 'info', | ||
601 | title: 'Franz', | ||
602 | message: 'Franz', | ||
603 | detail: `Version: ${remote.app.getVersion()}\nRelease: ${process.versions.electron} / ${process.platform} / ${process.arch}`, | ||
604 | }); | ||
605 | }, | ||
606 | }; | ||
607 | |||
608 | if (isMac) { | ||
204 | // Edit menu. | 609 | // Edit menu. |
205 | tpl[1].submenu.push( | 610 | tpl[1].submenu.push( |
206 | { | 611 | { |
207 | type: 'separator', | 612 | type: 'separator', |
208 | }, | 613 | }, |
209 | { | 614 | { |
210 | label: 'Speech', | 615 | label: intl.formatMessage(menuItems.speech), |
211 | submenu: [ | 616 | submenu: [ |
212 | { | 617 | { |
618 | label: intl.formatMessage(menuItems.startSpeaking), | ||
213 | role: 'startspeaking', | 619 | role: 'startspeaking', |
214 | }, | 620 | }, |
215 | { | 621 | { |
622 | label: intl.formatMessage(menuItems.stopSpeaking), | ||
216 | role: 'stopspeaking', | 623 | role: 'stopspeaking', |
217 | }, | 624 | }, |
218 | ], | 625 | ], |
219 | }, | 626 | }, |
220 | ); | 627 | ); |
628 | |||
629 | tpl[4].submenu.unshift(about, { | ||
630 | type: 'separator', | ||
631 | }); | ||
221 | } else { | 632 | } else { |
222 | tpl[4].submenu.unshift({ | 633 | tpl[0].submenu = [ |
223 | role: 'about', | 634 | { |
224 | click: () => { | 635 | label: intl.formatMessage(menuItems.settings), |
225 | dialog.showMessageBox({ | 636 | accelerator: 'Ctrl+P', |
226 | type: 'info', | 637 | click: () => { |
227 | title: 'Franz', | 638 | this.actions.ui.openSettings({ path: 'app' }); |
228 | message: 'Franz', | 639 | }, |
229 | detail: `Version: ${remote.app.getVersion()}\nRelease: ${process.versions.electron} / ${process.platform} / ${process.arch}`, | ||
230 | }); | ||
231 | }, | 640 | }, |
232 | }); | 641 | { |
233 | } | 642 | type: 'separator', |
643 | }, | ||
644 | { | ||
645 | label: intl.formatMessage(menuItems.quit), | ||
646 | accelerator: 'Alt+F4', | ||
647 | click: () => { | ||
648 | app.quit(); | ||
649 | }, | ||
650 | }, | ||
651 | ]; | ||
234 | 652 | ||
235 | const serviceTpl = this.serviceTpl; | 653 | tpl[5].submenu.push({ |
654 | type: 'separator', | ||
655 | }, about); | ||
656 | } | ||
236 | 657 | ||
237 | serviceTpl.unshift({ | 658 | serviceTpl.unshift({ |
238 | label: 'Add new Service', | 659 | label: intl.formatMessage(menuItems.addNewService), |
239 | accelerator: 'CmdOrCtrl+N', | 660 | accelerator: `${cmdKey}+N`, |
240 | click: () => { | 661 | click: () => { |
241 | this.actions.ui.openSettings({ path: 'recipes' }); | 662 | this.actions.ui.openSettings({ path: 'recipes' }); |
242 | }, | 663 | }, |
@@ -245,9 +666,10 @@ export default class FranzMenu { | |||
245 | }); | 666 | }); |
246 | 667 | ||
247 | if (serviceTpl.length > 0) { | 668 | if (serviceTpl.length > 0) { |
248 | tpl[isMac ? 3 : 2].submenu = toJS(this.serviceTpl); | 669 | tpl[3].submenu = serviceTpl; |
249 | } | 670 | } |
250 | 671 | ||
672 | this.currentTemplate = tpl; | ||
251 | const menu = Menu.buildFromTemplate(tpl); | 673 | const menu = Menu.buildFromTemplate(tpl); |
252 | Menu.setApplicationMenu(menu); | 674 | Menu.setApplicationMenu(menu); |
253 | } | 675 | } |
@@ -258,7 +680,7 @@ export default class FranzMenu { | |||
258 | if (this.stores.user.isLoggedIn) { | 680 | if (this.stores.user.isLoggedIn) { |
259 | return services.map((service, i) => ({ | 681 | return services.map((service, i) => ({ |
260 | label: this._getServiceName(service), | 682 | label: this._getServiceName(service), |
261 | accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null, | 683 | accelerator: i <= 9 ? `${cmdKey}+${i + 1}` : null, |
262 | type: 'radio', | 684 | type: 'radio', |
263 | checked: service.isActive, | 685 | checked: service.isActive, |
264 | click: () => { | 686 | click: () => { |
diff --git a/src/styles/layout.scss b/src/styles/layout.scss index afdd7dec7..964a9fcea 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss | |||
@@ -6,7 +6,11 @@ html { | |||
6 | 6 | ||
7 | .app { | 7 | .app { |
8 | display: flex; | 8 | display: flex; |
9 | flex-direction: row; | 9 | flex-direction: column; |
10 | |||
11 | .app__content { | ||
12 | display: flex; | ||
13 | } | ||
10 | 14 | ||
11 | .app__service { | 15 | .app__service { |
12 | display: flex; | 16 | display: flex; |
@@ -15,6 +19,10 @@ html { | |||
15 | } | 19 | } |
16 | } | 20 | } |
17 | 21 | ||
22 | .electron-app-title-bar { | ||
23 | z-index: 99999999; | ||
24 | } | ||
25 | |||
18 | .window-draggable { | 26 | .window-draggable { |
19 | position: absolute; | 27 | position: absolute; |
20 | width: 100%; | 28 | width: 100%; |
diff --git a/src/styles/main.scss b/src/styles/main.scss index 446bdca14..784a04d3d 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss | |||
@@ -4,6 +4,7 @@ $mdi-font-path: '../node_modules/mdi/fonts'; | |||
4 | } | 4 | } |
5 | 5 | ||
6 | @import './node_modules/mdi/scss/materialdesignicons.scss'; | 6 | @import './node_modules/mdi/scss/materialdesignicons.scss'; |
7 | @import './node_modules/electron-react-titlebar/assets/style'; | ||
7 | 8 | ||
8 | // modules | 9 | // modules |
9 | @import './reset.scss'; | 10 | @import './reset.scss'; |
@@ -28,6 +29,7 @@ $mdi-font-path: '../node_modules/mdi/fonts'; | |||
28 | @import './subscription-popup.scss'; | 29 | @import './subscription-popup.scss'; |
29 | @import './content-tabs.scss'; | 30 | @import './content-tabs.scss'; |
30 | @import './invite.scss'; | 31 | @import './invite.scss'; |
32 | @import './title-bar.scss'; | ||
31 | 33 | ||
32 | // form | 34 | // form |
33 | @import './input.scss'; | 35 | @import './input.scss'; |
diff --git a/src/styles/title-bar.scss b/src/styles/title-bar.scss new file mode 100644 index 000000000..492245e2f --- /dev/null +++ b/src/styles/title-bar.scss | |||
@@ -0,0 +1,50 @@ | |||
1 | #electron-app-title-bar { | ||
2 | background: $theme-gray-lightest; | ||
3 | border-bottom: 0; | ||
4 | box-shadow: 0px 0 8px rgba(#000, 0.1); | ||
5 | |||
6 | span { | ||
7 | line-height: normal; | ||
8 | } | ||
9 | |||
10 | div { | ||
11 | height: auto; | ||
12 | } | ||
13 | |||
14 | .toolbar-dropdown { | ||
15 | &.open { | ||
16 | box-shadow: 0px 0 8px rgba(#000, 0.1); | ||
17 | } | ||
18 | |||
19 | &:not(.open) { | ||
20 | .menu-item .menu-label { | ||
21 | opacity: 1; | ||
22 | } | ||
23 | |||
24 | &>.toolbar-button > button:hover { | ||
25 | background: $theme-brand-primary; | ||
26 | } | ||
27 | } | ||
28 | } | ||
29 | |||
30 | .list-item { | ||
31 | .menu-item { | ||
32 | margin: 4px; | ||
33 | border-radius: $theme-border-radius-small; | ||
34 | } | ||
35 | &.selected { | ||
36 | // background: $theme-brand-primary; | ||
37 | background: none; | ||
38 | |||
39 | .menu-item { | ||
40 | background: $theme-brand-primary; | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | .menu-pane { | ||
46 | box-shadow: 0px 0 10px rgba(#000, 0.5); | ||
47 | border-bottom-left-radius: $theme-border-radius-small; | ||
48 | border-bottom-right-radius: $theme-border-radius-small; | ||
49 | } | ||
50 | } | ||
@@ -1367,7 +1367,7 @@ circular-json@^0.3.1: | |||
1367 | version "0.3.3" | 1367 | version "0.3.3" |
1368 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" | 1368 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" |
1369 | 1369 | ||
1370 | classnames@^2.2.0, classnames@^2.2.5: | 1370 | classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.5: |
1371 | version "2.2.5" | 1371 | version "2.2.5" |
1372 | resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" | 1372 | resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" |
1373 | 1373 | ||
@@ -1766,6 +1766,10 @@ doctrine@^2.0.0: | |||
1766 | esutils "^2.0.2" | 1766 | esutils "^2.0.2" |
1767 | isarray "^1.0.0" | 1767 | isarray "^1.0.0" |
1768 | 1768 | ||
1769 | "dom-helpers@^2.4.0 || ^3.0.0": | ||
1770 | version "3.3.1" | ||
1771 | resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" | ||
1772 | |||
1769 | dom-helpers@^3.2.0: | 1773 | dom-helpers@^3.2.0: |
1770 | version "3.2.1" | 1774 | version "3.2.1" |
1771 | resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" | 1775 | resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" |
@@ -1995,6 +1999,13 @@ electron-publish@19.15.0: | |||
1995 | fs-extra-p "^4.3.0" | 1999 | fs-extra-p "^4.3.0" |
1996 | mime "^1.3.6" | 2000 | mime "^1.3.6" |
1997 | 2001 | ||
2002 | electron-react-titlebar@^0.7.1: | ||
2003 | version "0.7.1" | ||
2004 | resolved "https://registry.yarnpkg.com/electron-react-titlebar/-/electron-react-titlebar-0.7.1.tgz#d56517d01ef0e935caa994e9b577dfd63a56c66c" | ||
2005 | dependencies: | ||
2006 | lodash "^4.17.4" | ||
2007 | react-virtualized "^9.7.6" | ||
2008 | |||
1998 | electron-rebuild@^1.6.0: | 2009 | electron-rebuild@^1.6.0: |
1999 | version "1.6.0" | 2010 | version "1.6.0" |
2000 | resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.6.0.tgz#e8d26f4d8e9fe5388df35864b3658e5cfd4dcb7e" | 2011 | resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.6.0.tgz#e8d26f4d8e9fe5388df35864b3658e5cfd4dcb7e" |
@@ -2450,6 +2461,18 @@ fast-levenshtein@~2.0.4: | |||
2450 | version "2.0.6" | 2461 | version "2.0.6" |
2451 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" | 2462 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" |
2452 | 2463 | ||
2464 | fbjs@^0.8.16: | ||
2465 | version "0.8.16" | ||
2466 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" | ||
2467 | dependencies: | ||
2468 | core-js "^1.0.0" | ||
2469 | isomorphic-fetch "^2.1.1" | ||
2470 | loose-envify "^1.0.0" | ||
2471 | object-assign "^4.1.0" | ||
2472 | promise "^7.1.1" | ||
2473 | setimmediate "^1.0.5" | ||
2474 | ua-parser-js "^0.7.9" | ||
2475 | |||
2453 | fbjs@^0.8.9: | 2476 | fbjs@^0.8.9: |
2454 | version "0.8.14" | 2477 | version "0.8.14" |
2455 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.14.tgz#d1dbe2be254c35a91e09f31f9cd50a40b2a0ed1c" | 2478 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.14.tgz#d1dbe2be254c35a91e09f31f9cd50a40b2a0ed1c" |
@@ -4058,7 +4081,7 @@ longest@^1.0.1: | |||
4058 | version "1.0.1" | 4081 | version "1.0.1" |
4059 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" | 4082 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" |
4060 | 4083 | ||
4061 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: | 4084 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1: |
4062 | version "1.3.1" | 4085 | version "1.3.1" |
4063 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" | 4086 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" |
4064 | dependencies: | 4087 | dependencies: |
@@ -4948,6 +4971,14 @@ prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8: | |||
4948 | fbjs "^0.8.9" | 4971 | fbjs "^0.8.9" |
4949 | loose-envify "^1.3.1" | 4972 | loose-envify "^1.3.1" |
4950 | 4973 | ||
4974 | prop-types@^15.6.0: | ||
4975 | version "15.6.0" | ||
4976 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" | ||
4977 | dependencies: | ||
4978 | fbjs "^0.8.16" | ||
4979 | loose-envify "^1.3.1" | ||
4980 | object-assign "^4.1.1" | ||
4981 | |||
4951 | proxy-middleware@~0.15.0: | 4982 | proxy-middleware@~0.15.0: |
4952 | version "0.15.0" | 4983 | version "0.15.0" |
4953 | resolved "https://registry.yarnpkg.com/proxy-middleware/-/proxy-middleware-0.15.0.tgz#a3fdf1befb730f951965872ac2f6074c61477a56" | 4984 | resolved "https://registry.yarnpkg.com/proxy-middleware/-/proxy-middleware-0.15.0.tgz#a3fdf1befb730f951965872ac2f6074c61477a56" |
@@ -5126,6 +5157,16 @@ react-transition-group@^1.2.0: | |||
5126 | prop-types "^15.5.6" | 5157 | prop-types "^15.5.6" |
5127 | warning "^3.0.0" | 5158 | warning "^3.0.0" |
5128 | 5159 | ||
5160 | react-virtualized@^9.7.6: | ||
5161 | version "9.18.5" | ||
5162 | resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.18.5.tgz#42dd390ebaa7ea809bfcaf775d39872641679b89" | ||
5163 | dependencies: | ||
5164 | babel-runtime "^6.26.0" | ||
5165 | classnames "^2.2.3" | ||
5166 | dom-helpers "^2.4.0 || ^3.0.0" | ||
5167 | loose-envify "^1.3.0" | ||
5168 | prop-types "^15.6.0" | ||
5169 | |||
5129 | react@^15.4.1: | 5170 | react@^15.4.1: |
5130 | version "15.6.1" | 5171 | version "15.6.1" |
5131 | resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" | 5172 | resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" |