+ }
+}
+
+module.exports = Menubar
diff --git a/app/renderer/components/windowCaptionButtons.js b/app/renderer/components/windowCaptionButtons.js
new file mode 100644
index 00000000000..4b91ddae83c
--- /dev/null
+++ b/app/renderer/components/windowCaptionButtons.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const os = require('os')
+const React = require('react')
+const ImmutableComponent = require('../../../js/components/immutableComponent')
+const locale = require('../../../js/l10n')
+const currentWindow = require('../currentWindow')
+
+class WindowCaptionButtons extends ImmutableComponent {
+ constructor () {
+ super()
+ this.onDoubleClick = this.onDoubleClick.bind(this)
+ this.onMinimizeClick = this.onMinimizeClick.bind(this)
+ this.onMaximizeClick = this.onMaximizeClick.bind(this)
+ this.onCloseClick = this.onCloseClick.bind(this)
+ this.osClass = this.getPlatformCssClass()
+ }
+
+ get buttonClass () {
+ return (this.props.windowMaximized ? 'fullscreen' : '')
+ }
+
+ getPlatformCssClass () {
+ switch (os.platform()) {
+ case 'win32':
+ if (/6.1./.test(os.release())) {
+ return 'win7'
+ } else {
+ return 'win10'
+ }
+ default:
+ return 'hidden'
+ }
+ }
+
+ onMinimizeClick (e) {
+ currentWindow.minimize()
+ }
+
+ onMaximizeClick (e) {
+ return (!currentWindow.isMaximized()) ? currentWindow.maximize() : currentWindow.unmaximize()
+ }
+
+ onCloseClick (e) {
+ currentWindow.close()
+ }
+
+ onDoubleClick (e) {
+ if (!e.target.className.includes('navigatorWrapper')) {
+ return
+ }
+ this.onMaximizeClick(e)
+ }
+
+ render () {
+ return
+
+
+
+
+
+
+ }
+}
+
+module.exports = WindowCaptionButtons
diff --git a/app/sessionStore.js b/app/sessionStore.js
index 0df60aa1a22..63bc076f908 100644
--- a/app/sessionStore.js
+++ b/app/sessionStore.js
@@ -412,6 +412,7 @@ module.exports.defaultAppState = () => {
guid: [],
timestamp: 0
}
- }
+ },
+ menubar: {}
}
}
diff --git a/docs/appActions.md b/docs/appActions.md
index 0cc3cbde426..0b8e110151a 100644
--- a/docs/appActions.md
+++ b/docs/appActions.md
@@ -403,6 +403,16 @@ Dispatches a message when appWindowId loses focus
+### setMenubarTemplate(menubarTemplate)
+
+Saves current menubar template for use w/ Windows titlebar
+
+**Parameters**
+
+**menubarTemplate**: `Object`, JSON used to build the menu
+
+
+
### networkConnected()
Dispatches a message when the network is re-connected
diff --git a/docs/state.md b/docs/state.md
index b6cf143c2ee..9ddfe464ba0 100644
--- a/docs/state.md
+++ b/docs/state.md
@@ -219,6 +219,9 @@ AppStore
newtab: {
gridLayout: string // 'small', 'medium', 'large'
}
+ },
+ menubar: {
+ template: object // windows only: template object with Menubar control
}
}
```
@@ -367,6 +370,12 @@ WindowStore
},
releaseNotes: {
isVisible: boolean, // Whether or not to show release notes
+ },
+ menubar: { // windows only
+ isVisible: boolean, // true if Menubar control is visible
+ selectedLabel: string, // label of menu that is selected (or null for none selected)
+ selectedIndex: number, // index of the selected context menu item
+ lastFocusedSelector: string // selector for the last selected element (browser ui, not frame content)
}
},
searchDetail: {
diff --git a/docs/windowActions.md b/docs/windowActions.md
index 8031d182346..4a6c6c7fcb1 100644
--- a/docs/windowActions.md
+++ b/docs/windowActions.md
@@ -788,6 +788,90 @@ blocked active mixed content on
+### toggleMenubarVisible(isVisible)
+
+(Windows only)
+Dispatches a message to indicate the custom rendered Menubar should be toggled (shown/hidden)
+
+**Parameters**
+
+**isVisible**: `boolean`, (optional)
+
+
+
+### clickMenubarSubmenu(label)
+
+(Windows only)
+Used to trigger the click() action for a menu
+Called from the Menubar control, handled in menu.js
+
+**Parameters**
+
+**label**: `string`, text of the label that was clicked
+
+
+
+### setMenubarSelectedLabel(label)
+
+(Windows only)
+Used to track which menubar item is currently selected (or null for none selected)
+
+**Parameters**
+
+**label**: `string`, text of the menubar item label that was clicked (file, edit, etc)
+
+
+
+### resetMenuState()
+
+Used by `main.js` when click happens on content area (not on a link or react control).
+- closes context menu
+- closes popup menu
+- nulls out menubar item selected (Windows only)
+- hides menubar if auto-hide preference is set (Windows only)
+
+
+
+### setSubmenuSelectedIndex(index)
+
+(Windows only)
+Used to track selected index of a context menu
+Needed because arrow keys can be used to navigate the custom menu
+
+**Parameters**
+
+**index**: `number`, zero based index of the item.
+ Index excludes menu separators and hidden items.
+
+
+
+### setLastFocusedSelector(selector)
+
+(Windows only at the moment)
+Used to track last selected element (typically the URL bar or the frame)
+Important because focus is lost when using the custom menu and needs
+to be returned in order for the cut/copy operation to work
+
+**Parameters**
+
+**selector**: `string`, selector used w/ querySelectorAll to return focus
+ after a menu item is selected (via the custom titlebar / menubar)
+
+
+
+### gotResponseDetails(tabId, details)
+
+Used to get response details (such as the HTTP response code) from a response
+See `eventStore.js` for an example use-case
+
+**Parameters**
+
+**tabId**: `number`, the tab id to set
+
+**details**: `Object`, object containing response details
+
+
+
* * *
diff --git a/img/windows/close.svg b/img/windows/close.svg
new file mode 100644
index 00000000000..fcca53e1a5f
--- /dev/null
+++ b/img/windows/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/js/actions/appActions.js b/js/actions/appActions.js
index b32c7e478c0..ce8fbb88ae7 100644
--- a/js/actions/appActions.js
+++ b/js/actions/appActions.js
@@ -466,6 +466,17 @@ const appActions = {
})
},
+ /**
+ * Saves current menubar template for use w/ Windows titlebar
+ * @param {Object} menubarTemplate - JSON used to build the menu
+ */
+ setMenubarTemplate: function (menubarTemplate) {
+ AppDispatcher.dispatch({
+ actionType: AppConstants.APP_SET_MENUBAR_TEMPLATE,
+ menubarTemplate
+ })
+ },
+
/**
* Dispatches a message when the network is re-connected
* after being disconnected
diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js
index 820159d4675..60f62d7cf3b 100644
--- a/js/actions/windowActions.js
+++ b/js/actions/windowActions.js
@@ -1007,6 +1007,92 @@ const windowActions = {
})
},
+ /**
+ * (Windows only)
+ * Dispatches a message to indicate the custom rendered Menubar should be toggled (shown/hidden)
+ * @param {boolean} isVisible (optional)
+ */
+ toggleMenubarVisible: function (isVisible, defaultLabel) {
+ dispatch({
+ actionType: WindowConstants.WINDOW_TOGGLE_MENUBAR_VISIBLE,
+ isVisible,
+ defaultLabel
+ })
+ },
+
+ /**
+ * (Windows only)
+ * Used to trigger the click() action for a menu
+ * Called from the Menubar control, handled in menu.js
+ * @param {string} label - text of the label that was clicked
+ */
+ clickMenubarSubmenu: function (label) {
+ dispatch({
+ actionType: WindowConstants.WINDOW_CLICK_MENUBAR_SUBMENU,
+ label
+ })
+ },
+
+ /**
+ * (Windows only)
+ * Used to track which menubar item is currently selected (or null for none selected)
+ * @param {string} label - text of the menubar item label that was clicked (file, edit, etc)
+ */
+ setMenubarSelectedLabel: function (label) {
+ dispatch({
+ actionType: WindowConstants.WINDOW_SET_MENUBAR_SELECTED_LABEL,
+ label
+ })
+ },
+
+ /**
+ * Used by `main.js` when click happens on content area (not on a link or react control).
+ * - closes context menu
+ * - closes popup menu
+ * - nulls out menubar item selected (Windows only)
+ * - hides menubar if auto-hide preference is set (Windows only)
+ */
+ resetMenuState: function () {
+ dispatch({
+ actionType: WindowConstants.WINDOW_RESET_MENU_STATE
+ })
+ },
+
+ /**
+ * (Windows only)
+ * Used to track selected index of a context menu
+ * Needed because arrow keys can be used to navigate the custom menu
+ * @param {number} index - zero based index of the item.
+ * Index excludes menu separators and hidden items.
+ */
+ setSubmenuSelectedIndex: function (index) {
+ dispatch({
+ actionType: WindowConstants.WINDOW_SET_SUBMENU_SELECTED_INDEX,
+ index
+ })
+ },
+
+ /**
+ * (Windows only at the moment)
+ * Used to track last selected element (typically the URL bar or the frame)
+ * Important because focus is lost when using the custom menu and needs
+ * to be returned in order for the cut/copy operation to work
+ * @param {string} selector - selector used w/ querySelectorAll to return focus
+ * after a menu item is selected (via the custom titlebar / menubar)
+ */
+ setLastFocusedSelector: function (selector) {
+ dispatch({
+ actionType: WindowConstants.WINDOW_SET_LAST_FOCUSED_SELECTOR,
+ selector
+ })
+ },
+
+ /**
+ * Used to get response details (such as the HTTP response code) from a response
+ * See `eventStore.js` for an example use-case
+ * @param {number} tabId - the tab id to set
+ * @param {Object} details - object containing response details
+ */
gotResponseDetails: function (tabId, details) {
dispatch({
actionType: WindowConstants.WINDOW_GOT_RESPONSE_DETAILS,
diff --git a/js/components/contextMenu.js b/js/components/contextMenu.js
index 636f123ba1a..95045cdfdd0 100644
--- a/js/components/contextMenu.js
+++ b/js/components/contextMenu.js
@@ -8,6 +8,8 @@ const ImmutableComponent = require('./immutableComponent')
const windowActions = require('../actions/windowActions')
const cx = require('../lib/classSet.js')
const KeyCodes = require('../constants/keyCodes')
+const { formatAccelerator } = require('../../app/common/lib/formatUtil')
+const separatorMenuItem = require('../../app/common/commonMenu').separatorMenuItem
class ContextMenuItem extends ImmutableComponent {
componentDidMount () {
@@ -27,11 +29,20 @@ class ContextMenuItem extends ImmutableComponent {
get hasSubmenu () {
return this.submenu && this.submenu.size > 0
}
+ get accelerator () {
+ const accelerator = this.props.contextMenuItem.get('accelerator')
+ return accelerator && typeof accelerator === 'string'
+ ? accelerator.trim()
+ : null
+ }
+ get hasAccelerator () {
+ return this.accelerator !== null
+ }
onClick (clickAction, shouldHide, e) {
e.stopPropagation()
if (clickAction) {
if (shouldHide) {
- windowActions.setContextMenuDetail()
+ windowActions.resetMenuState()
}
clickAction(e)
}
@@ -143,7 +154,8 @@ class ContextMenuItem extends ImmutableComponent {
contextMenuItem: true,
hasFaIcon: faIcon,
checkedMenuItem: this.props.contextMenuItem.get('checked'),
- hasIcon: icon || faIcon
+ hasIcon: icon || faIcon,
+ selectedByKeyboard: this.props.selected
})}
role='listitem'
draggable={this.props.contextMenuItem.get('draggable')}
@@ -180,7 +192,12 @@ class ContextMenuItem extends ImmutableComponent {
- : null
+ : this.hasAccelerator
+ ?
+
+ {formatAccelerator(this.accelerator)}
+
+ : null
}
}
@@ -195,17 +212,33 @@ class ContextMenuSingle extends ImmutableComponent {
if (this.props.y) {
styles.top = this.props.y
}
+ const visibleMenuItems = this.props.template.filter((element) => {
+ return element.has('visible')
+ ? element.get('visible')
+ : true
+ })
+
+ let index = 0
return