Skip to content

Commit 3d4ad0b

Browse files
authored
Merge pull request #104 from Coding/hackape/tab-group-refine
rewrite Tab, Pane and DragAndDrop modules with mobx
2 parents b0d07ba + 19550ed commit 3d4ad0b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1261
-1146
lines changed

app/commands/commandBindings/file.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* @flow weak */
22
import { bindActionCreators } from 'redux'
33
import store, { getState, dispatch } from '../../store'
4+
import mobxStore from '../../mobxStore'
45
import { path as pathUtil } from '../../utils'
56
import api from '../../backendAPI'
67
import * as _Modal from '../../components/Modal/actions'
7-
import * as Tab from '../../components/Tab'
8+
import * as TabActions from 'commons/Tab/actions'
89
import { notify } from '../../components/Notification/actions'
910

1011
const Modal = bindActionCreators(_Modal, dispatch)
@@ -74,9 +75,9 @@ export default {
7475
}).then(createFolderAtPath)
7576
},
7677
'file:save': (c) => {
77-
const { TabState } = getState()
78-
const activeTab = Tab.selectors.getActiveTab(TabState)
79-
const content = activeTab ? ide.editors[activeTab.id].getValue() : ''
78+
const { EditorTabState } = mobxStore
79+
const activeTab = EditorTabState.activeTab
80+
const content = activeTab ? activeTab.editor.getValue() : ''
8081

8182
if (!activeTab.path) {
8283
const createFile = createFileWithContent(content)
@@ -86,17 +87,17 @@ export default {
8687
selectionRange: [1, '/untitled'.length]
8788
})
8889
.then(createFile)
89-
.then(path => dispatch(Tab.actions.updateTab({
90+
.then(path => dispatch(TabActions.updateTab({
9091
id: activeTab.id,
9192
path,
9293
title: path.replace(/^.*\/([^\/]+$)/, '$1')
9394
})))
94-
.then(() => dispatch(Tab.actions.updateTabFlags(activeTab.id, 'modified', false)))
95+
.then(() => dispatch(TabActions.updateTabFlags(activeTab.id, 'modified', false)))
9596
} else {
9697
api.writeFile(activeTab.path, content)
9798
.then(() => {
98-
dispatch(Tab.actions.updateTabFlags(activeTab.id, 'modified', false))
99-
dispatch(Tab.actions.updateTab({
99+
dispatch(TabActions.updateTabFlags(activeTab.id, 'modified', false))
100+
dispatch(TabActions.updateTab({
100101
id: activeTab.id, content: { body: content }
101102
}))
102103
})

app/commands/commandBindings/tab.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
/* @flow weak */
2-
import store, { dispatch as $d } from '../../store'
3-
import * as Tab from '../../components/Tab/actions'
4-
import * as PaneActions from '../../components/Pane/actions'
2+
import { dispatch as $d } from '../../store'
3+
import store from 'app/mobxStore'
4+
import * as Tab from 'commons/Tab/actions'
5+
import * as PaneActions from 'components/Pane/actions'
56

67
export default {
78
'tab:close': c => {
8-
$d(Tab.removeTab(c.context.id))
9+
Tab.removeTab(c.context.id)
910
},
1011

1112
'tab:close_other': c => {
12-
$d(Tab.removeOtherTab(c.context.id))
13+
Tab.removeOtherTab(c.context.id)
1314
},
1415

1516
'tab:close_all': c => {
16-
$d(Tab.removeAllTab(c.context.id))
17+
Tab.removeAllTab(c.context.id)
1718
},
1819

1920
'tab:split_v': c => {
20-
const panes = store.getState().PaneState.panes
21-
const pane = Object.values(panes).find((pane) => (
21+
const panes = store.PaneState.panes
22+
const pane = panes.values().find(pane => (
2223
pane.content && pane.content.type === 'tabGroup' && pane.content.id === c.context.tabGroupId
2324
))
2425
$d(PaneActions.splitTo(pane.id, 'bottom'))

app/commons/Tab/TabBar.jsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import cx from 'classnames'
3+
import { observer } from 'mobx-react'
4+
import { dnd } from 'utils'
5+
import { defaultProps } from 'utils/decorators'
6+
import TabLabel from './TabLabel'
7+
import Menu from 'components/Menu'
8+
import ContextMenu from 'components/ContextMenu'
9+
10+
@defaultProps(props => ({
11+
addTab: () => props.tabGroup.addTab(),
12+
}))
13+
@observer
14+
class TabBar extends Component {
15+
static propTypes = {
16+
tabGroup: PropTypes.object.isRequired,
17+
contextMenuItems: PropTypes.array.isRequired,
18+
addTab: PropTypes.func,
19+
closePane: PropTypes.func,
20+
};
21+
22+
constructor (props) {
23+
super(props)
24+
this.state = {
25+
showDropdownMenu: false,
26+
showContextMenu: false,
27+
contextMenuPos: {},
28+
contextMenuContext: null,
29+
}
30+
}
31+
32+
render () {
33+
const {
34+
tabGroup,
35+
addTab,
36+
contextMenuItems,
37+
} = this.props
38+
39+
const tabBarId = `tab_bar_${tabGroup.id}`
40+
return (
41+
<div id={tabBarId}
42+
className='tab-bar'
43+
data-droppable='TABBAR'
44+
onDoubleClick={addTab}
45+
>
46+
<ul className='tab-labels'>
47+
{tabGroup.tabs.map(tab =>
48+
<TabLabel tab={tab} key={tab.id} openContextMenu={this.openContextMenu} />
49+
)}
50+
</ul>
51+
{dnd.target.id === tabBarId ? <div className='tab-label-insert-pos'></div>: null}
52+
<div className='tab-add-btn' onClick={addTab} >
53+
<svg viewBox='0 0 12 16' version='1.1' aria-hidden='true'>
54+
<path fillRule='evenodd' d='M12 9H7v5H5V9H0V7h5V2h2v5h5z'></path>
55+
</svg>
56+
</div>
57+
<div className='tab-show-list'
58+
onClick={e => { e.stopPropagation(); this.setState({ showDropdownMenu: true }) }}
59+
>
60+
<i className='fa fa-sort-desc' />
61+
{this.renderDropdownMenu()}
62+
</div>
63+
<ContextMenu
64+
items={contextMenuItems}
65+
isActive={this.state.showContextMenu}
66+
pos={this.state.contextMenuPos}
67+
context={this.state.contextMenuContext}
68+
deactivate={() => this.setState({ showContextMenu: false })}
69+
/>
70+
</div>
71+
)
72+
}
73+
74+
openContextMenu = (e, context) => {
75+
e.stopPropagation()
76+
e.preventDefault()
77+
78+
this.setState({
79+
showContextMenu: true,
80+
contextMenuPos: { x: e.clientX, y: e.clientY },
81+
contextMenuContext: context,
82+
})
83+
}
84+
85+
renderDropdownMenu () {
86+
if (!this.state.showDropdownMenu) return null
87+
const dropdownMenuItems = this.makeDropdownMenuItems()
88+
if (!dropdownMenuItems.length) return null
89+
return <Menu className='top-down to-left'
90+
items={dropdownMenuItems}
91+
style={{ right: '2px' }}
92+
deactivate={e=>this.setState({ showDropdownMenu: false })}
93+
/>
94+
95+
}
96+
97+
makeDropdownMenuItems = () => {
98+
let baseItems = this.props.tabGroup.siblings.length === 0 ? []
99+
: [{
100+
name: 'Close Pane',
101+
command: this.props.closePane,
102+
}]
103+
const tabs = this.props.tabGroup.tabs
104+
const tabLabelsItem = tabs && tabs.map(tab => ({
105+
name: tab.title || 'untitled',
106+
command: e => tab.activate(),
107+
}))
108+
109+
if (tabLabelsItem.length) {
110+
if (!baseItems.length) return tabLabelsItem
111+
return baseItems.concat({ name: '-' }, tabLabelsItem)
112+
} else {
113+
return baseItems
114+
}
115+
}
116+
}
117+
118+
export default TabBar

app/commons/Tab/TabContent.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { PropTypes } from 'react'
2+
import cx from 'classnames'
3+
import { observer } from 'mobx-react'
4+
5+
export const TabContent = ({ children }) => (
6+
<div className='tab-content'>
7+
<div className='tab-content-container'>{children}</div>
8+
</div>
9+
)
10+
11+
export const TabContentItem = observer(({ tab, children }) => (
12+
<div className={cx('tab-content-item', { active: tab.isActive })}>
13+
{children}
14+
</div>
15+
))
16+
17+
TabContentItem.propTypes = {
18+
tab: PropTypes.object.isRequired,
19+
}
20+

app/commons/Tab/TabLabel.jsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import cx from 'classnames'
3+
import { observer } from 'mobx-react'
4+
import { dnd } from 'utils'
5+
import { defaultProps } from 'utils/decorators'
6+
import { dispatch } from '../../store'
7+
8+
let TabLabel = observer(({tab, removeTab, activateTab, openContextMenu}) => {
9+
const tabLabelId = `tab_label_${tab.id}`
10+
return (
11+
<li className={cx('tab-label', {
12+
active: tab.isActive,
13+
modified: tab.flags.modified
14+
})}
15+
id={tabLabelId}
16+
data-droppable='TABLABEL'
17+
draggable='true'
18+
onClick={e => activateTab(tab.id)}
19+
onDragStart={e => dnd.dragStart({ type: 'TAB', id: tab.id }) }
20+
onContextMenu={e => openContextMenu(e, tab)}
21+
>
22+
{dnd.target.id === tabLabelId ? <div className='tab-label-insert-pos'></div>: null}
23+
{tab.icon ? <div className={tab.icon}></div>: null}
24+
<div className='title'>{tab.title}</div>
25+
<div className='control'>
26+
<i className='close' onClick={e => { e.stopPropagation(); removeTab(tab.id) }}>×</i>
27+
<i className='dot'></i>
28+
</div>
29+
</li>
30+
)
31+
})
32+
33+
TabLabel.propTypes = {
34+
tab: PropTypes.object.isRequired,
35+
removeTab: PropTypes.func.isRequired,
36+
activateTab: PropTypes.func.isRequired,
37+
openContextMenu: PropTypes.func.isRequired,
38+
}
39+
40+
TabLabel = defaultProps(props => ({
41+
activateTab: function () {
42+
props.tab.activate()
43+
},
44+
removeTab: function () {
45+
props.tab.destroy()
46+
},
47+
}))(TabLabel)
48+
49+
export default TabLabel

app/components/Tab/actions.js renamed to app/commons/Tab/actions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @flow weak */
2-
import { createAction } from 'redux-actions'
2+
import { createAction } from 'utils/actions'
33

44
export const TAB_DISSOLVE_GROUP = 'TAB_DISSOLVE_GROUP'
55

app/commons/Tab/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import TabStateScope from './state'
2+
import TabBar from './TabBar'
3+
import { TabContent, TabContentItem } from './TabContent'
4+
5+
export { TabBar, TabContent, TabContentItem, TabStateScope }

0 commit comments

Comments
 (0)