diff --git a/app/commands/commandBindings/index.js b/app/commands/commandBindings/index.js
index e7534a3d..9d7c9a88 100644
--- a/app/commands/commandBindings/index.js
+++ b/app/commands/commandBindings/index.js
@@ -2,10 +2,12 @@ import git from './git'
import file from './file'
import misc from './misc'
import editor from './editor'
+import tab from './tab'
export default {
...git,
...file,
...misc,
- ...editor
+ ...editor,
+ ...tab
}
diff --git a/app/commands/commandBindings/tab.js b/app/commands/commandBindings/tab.js
new file mode 100644
index 00000000..f6268a2d
--- /dev/null
+++ b/app/commands/commandBindings/tab.js
@@ -0,0 +1,40 @@
+/* @flow weak */
+import store, { dispatch as $d } from '../../store'
+import * as Tab from '../../components/Tab/actions'
+import * as PaneActions from '../../components/Pane/actions'
+
+export default {
+ 'tab:close': c => {
+ $d(Tab.removeTab(c.context.id))
+ },
+
+ 'tab:close_other': c => {
+ $d(Tab.removeOtherTab(c.context.id))
+ },
+
+ 'tab:close_all': c => {
+ $d(Tab.removeAllTab(c.context.id))
+ },
+
+ 'tab:split_v': c => {
+ const panes = store.getState().PaneState.panes
+ const pane = Object.values(panes).find((pane) => (
+ pane.content && pane.content.type === 'tabGroup' && pane.content.id === c.context.tabGroupId
+ ))
+ $d(PaneActions.splitTo(pane.id, 'bottom'))
+ .then(newPaneId => {
+ $d(Tab.moveTabToPane(c.context.id, newPaneId))
+ })
+ },
+
+ 'tab:split_h': c => {
+ const panes = store.getState().PaneState.panes
+ const pane = Object.values(panes).find((pane) => (
+ pane.content && pane.content.type === 'tabGroup' && pane.content.id === c.context.tabGroupId
+ ))
+ $d(PaneActions.splitTo(pane.id, 'right'))
+ .then(newPaneId => {
+ $d(Tab.moveTabToPane(c.context.id, newPaneId))
+ })
+ },
+}
diff --git a/app/components/ContextMenu/index.jsx b/app/components/ContextMenu/index.jsx
index 8c4c209e..ce50ee99 100644
--- a/app/components/ContextMenu/index.jsx
+++ b/app/components/ContextMenu/index.jsx
@@ -3,7 +3,7 @@ import React, { Component } from 'react'
import { connect } from 'react-redux'
import cx from 'classnames'
import Menu from '../Menu'
-import {setContext} from '../../commands'
+import {setContext} from '../../commands/dispatchCommand'
const ContextMenu = (props) => {
const {items, context, isActive, pos, deactivate} = props
diff --git a/app/components/Tab/TabBar.jsx b/app/components/Tab/TabBar.jsx
index 704d8120..d1ed91eb 100644
--- a/app/components/Tab/TabBar.jsx
+++ b/app/components/Tab/TabBar.jsx
@@ -5,7 +5,33 @@ import { dragStart } from '../DragAndDrop/actions';
import Menu from '../Menu'
import * as TabActions from './actions';
import * as PaneActions from '../Pane/actions';
-
+import ContextMenu from '../ContextMenu'
+
+const dividItem = { name: '-' }
+const items = [
+ {
+ name: 'Close',
+ icon: '',
+ command: 'tab:close'
+ }, {
+ name: 'Close Others',
+ icon: '',
+ command: 'tab:close_other'
+ }, {
+ name: 'Close All',
+ icon: '',
+ command: 'tab:close_all'
+ }, dividItem,
+ {
+ name: 'Vertical Split',
+ icon: '',
+ command: 'tab:split_v'
+ }, {
+ name: 'Horizontal Split',
+ icon: '',
+ command: 'tab:split_h'
+ }
+]
class _TabBar extends Component {
constructor (props) {
@@ -57,7 +83,7 @@ class _TabBar extends Component {
}
render () {
- const { tabIds, tabGroupId, isRootPane, addTab, closePane, isDraggedOver } = this.props
+ const { tabIds, tabGroupId, isRootPane, addTab, closePane, isDraggedOver, contextMenu, closeContextMenu } = this.props
return (
@@ -76,7 +102,13 @@ class _TabBar extends Component {
{this.renderDropdownMenu()}
-
+
)
}
@@ -87,16 +119,19 @@ const TabBar = connect((state, { tabIds, tabGroupId, containingPaneId }) => ({
isDraggedOver: state.DragAndDrop.meta
? state.DragAndDrop.meta.tabBarTargetId === `tab_bar_${tabGroupId}`
: false,
- isRootPane: state.PaneState.rootPaneId === containingPaneId
+ isRootPane: state.PaneState.rootPaneId === containingPaneId,
+ contextMenu: state.TabState.contextMenu
}), (dispatch, { tabGroupId, containingPaneId }) => ({
activateTab: (tabId) => dispatch(TabActions.activateTab(tabId)),
addTab: () => dispatch(TabActions.createTabInGroup(tabGroupId)),
- closePane: () => dispatch(PaneActions.closePane(containingPaneId))
+ closePane: () => dispatch(PaneActions.closePane(containingPaneId)),
+ openContextMenu: (e, node) => dispatch(TabActions.openContextMenu(e, node)),
+ closeContextMenu: () => dispatch(TabActions.closeContextMenu())
})
)(_TabBar)
-const _TabLabel = ({tab, isActive, isDraggedOver, removeTab, activateTab, dragStart}) => {
+const _TabLabel = ({tab, isActive, isDraggedOver, removeTab, activateTab, dragStart, openContextMenu}) => {
const possibleStatus = {
'modified': '*',
'warning': '!',
@@ -115,6 +150,7 @@ const _TabLabel = ({tab, isActive, isDraggedOver, removeTab, activateTab, dragSt
onClick={e => activateTab(tab.id)}
draggable='true'
onDragStart={e => dragStart({sourceType: 'TAB', sourceId: tab.id})}
+ onContextMenu={e => openContextMenu(e, tab)}
>
{isDraggedOver ? : null}
{tab.icon ? : null}
@@ -133,6 +169,8 @@ _TabLabel.propTypes = {
removeTab: PropTypes.func,
activateTab: PropTypes.func,
dragStart: PropTypes.func,
+ openContextMenu: PropTypes.func,
+ closeContextMenu: PropTypes.func
}
const TabLabel = connect((state, { tabId }) => {
@@ -146,6 +184,8 @@ const TabLabel = connect((state, { tabId }) => {
removeTab: (tabId) => dispatch(TabActions.removeTab(tabId)),
activateTab: (tabId) => dispatch(TabActions.activateTab(tabId)),
dragStart: (dragEventObj) => dispatch(dragStart(dragEventObj)),
+ openContextMenu: (e, node) => dispatch(TabActions.openContextMenu(e, node)),
+ closeContextMenu: () => dispatch(TabActions.closeContextMenu())
})
)(_TabLabel)
diff --git a/app/components/Tab/actions.js b/app/components/Tab/actions.js
index f99acd8f..e857807c 100644
--- a/app/components/Tab/actions.js
+++ b/app/components/Tab/actions.js
@@ -12,6 +12,12 @@ export const createTabInGroup = createAction(TAB_CREATE_IN_GROUP, (groupId, tab)
export const TAB_REMOVE = 'TAB_REMOVE'
export const removeTab = createAction(TAB_REMOVE, tabId => tabId)
+export const TAB_REMOVE_OTHER = 'TAB_REMOVE_OTHER'
+export const removeOtherTab = createAction(TAB_REMOVE_OTHER, tabId => tabId)
+
+export const TAB_REMOVE_ALL = 'TAB_REMOVE_ALL'
+export const removeAllTab = createAction(TAB_REMOVE_ALL, tabId => tabId)
+
export const TAB_ACTIVATE = 'TAB_ACTIVATE'
export const activateTab = createAction(TAB_ACTIVATE, tabId => tabId)
@@ -62,3 +68,18 @@ export const TAB_INSERT_AT = 'TAB_INSERT_AT'
export const insertTabAt = createAction(TAB_INSERT_AT,
(tabId, beforeTabId) => ({tabId, beforeTabId})
)
+
+export const TAB_CONTEXT_MENU_OPEN = 'TAB_CONTEXT_MENU_OPEN'
+export const openContextMenu = createAction(TAB_CONTEXT_MENU_OPEN, (e, node) => {
+ e.stopPropagation()
+ e.preventDefault()
+
+ return {
+ isActive: true,
+ pos: { x: e.clientX, y: e.clientY },
+ contextNode: node,
+ }
+})
+
+export const TAB_CONTEXT_MENU_CLOSE = 'TAB_CONTEXT_MENU_CLOSE'
+export const closeContextMenu = createAction(TAB_CONTEXT_MENU_CLOSE)
diff --git a/app/components/Tab/reducer.js b/app/components/Tab/reducer.js
index 6953cd4a..ef1bf442 100644
--- a/app/components/Tab/reducer.js
+++ b/app/components/Tab/reducer.js
@@ -14,7 +14,11 @@ import {
TAB_UPDATE_BY_PATH,
TAB_MOVE_TO_GROUP,
TAB_MOVE_TO_PANE,
- TAB_INSERT_AT
+ TAB_INSERT_AT,
+ TAB_CONTEXT_MENU_OPEN,
+ TAB_CONTEXT_MENU_CLOSE,
+ TAB_REMOVE_OTHER,
+ TAB_REMOVE_ALL
} from './actions'
import {
@@ -57,7 +61,12 @@ import {
const defaultState = {
tabGroups: {},
tabs: {},
- activeTabGroupId: ''
+ activeTabGroupId: '',
+ contextMenu: {
+ isActive: false,
+ pos: { x: 0, y: 0 },
+ contextNode: null,
+ }
}
const Tab = Model({
@@ -113,6 +122,36 @@ const _removeTab = (state, tab) => {
})
}
+const _removeOtherTab = (state, tab) => {
+ let nextState = state
+ nextState = _activateTab(state, tab)
+ const tabGroup = state.tabGroups[tab.tabGroupId]
+ const tabIdsToBeDeleted = _.without(tabGroup.tabIds, tab.id)
+ const nextTabs = _.omit(state.tabs, tabIdsToBeDeleted)
+ return update(nextState, {
+ tabGroups: {
+ [tab.tabGroupId]: {
+ tabIds: { $set: [tab.id] }
+ }
+ },
+ tabs: { $set: nextTabs }
+ })
+}
+
+const _removeAllTab = (state, tab) => {
+ const tabGroup = state.tabGroups[tab.tabGroupId]
+ const tabIdsToBeDeleted = tabGroup.tabIds
+ const nextTabs = _.omit(state.tabs, tabIdsToBeDeleted)
+ return update(state, {
+ tabGroups: {
+ [tab.tabGroupId]: {
+ tabIds: { $set: [] }
+ }
+ },
+ tabs: { $set: nextTabs }
+ })
+}
+
const _moveTabToGroup = (state, tab, tabGroup, insertIndex = tabGroup.tabIds.length) => {
// 1. remove it from original group
let nextState = _removeTab(state, tab)
@@ -193,6 +232,16 @@ const TabReducer = handleActions({
return _removeTab(state, tab)
},
+ [TAB_REMOVE_OTHER]: (state, action) => {
+ const tab = state.tabs[action.payload]
+ return _removeOtherTab(state, tab)
+ },
+
+ [TAB_REMOVE_ALL]: (state, action) => {
+ const tab = state.tabs[action.payload]
+ return _removeAllTab(state, tab)
+ },
+
[TAB_ACTIVATE]: (state, action) => {
const tab = state.tabs[action.payload]
let nextState = _activateTab(state, tab)
@@ -261,7 +310,21 @@ const TabReducer = handleActions({
const { targetTabGroupId, sourceTabGroupId } = action.payload
if (!targetTabGroupId) return update(state, {tabGroups: {$delete: sourceTabGroupId}})
return _mergeTabGroups(state, targetTabGroupId, sourceTabGroupId)
- }
+ },
+
+ [TAB_CONTEXT_MENU_OPEN]: (state, action) => (
+ update(state, {
+ contextMenu: { $merge: action.payload }
+ })
+ ),
+
+ [TAB_CONTEXT_MENU_CLOSE]: (state, action) => (
+ update(state, {
+ contextMenu: { $merge: {
+ isActive: false
+ } }
+ })
+ ),
}, defaultState)