Skip to content

drag and drop tab on tabbar #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/DragAndDrop/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const dragStart = createAction(DND_DRAG_START,
)

export const DND_DRAG_OVER = 'DND_DRAG_OVER'
export const dragOverTarget = createAction(DND_DRAG_OVER, target => target)
export const updateDragOverTarget = createAction(DND_DRAG_OVER, target => target)

export const DND_UPDATE_DRAG_OVER_META = 'DND_UPDATE_DRAG_OVER_META'
export const updateDragOverMeta = createAction(DND_UPDATE_DRAG_OVER_META, meta => meta)
Expand Down
38 changes: 32 additions & 6 deletions app/components/DragAndDrop/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import _ from 'lodash'
import { dragOverTarget, updateDragOverMeta, dragEnd } from './actions'
import { updateDragOverTarget, updateDragOverMeta, dragEnd } from './actions'
import * as PaneActions from '../Pane/actions'
import * as TabActions from '../Tab/actions'

Expand Down Expand Up @@ -58,13 +58,17 @@ class DragAndDrop extends Component {

if (!target) return
if (!prevTarget || target.id !== prevTarget.id) {
dispatch(dragOverTarget({id: target.id, type: target.type}))
dispatch(updateDragOverTarget({id: target.id, type: target.type}))
}

switch (source.type) {
case 'TAB':
switch (`${source.type}_on_${target.type}`) {
case 'TAB_on_PANE':
return this.dragTabOverPane(e, target)

case 'TAB_on_TABBAR':
case 'TAB_on_TABLABEL':
return this.dragTabOverTabBar(e, target)

default:
}
}
Expand All @@ -73,13 +77,25 @@ class DragAndDrop extends Component {
e.preventDefault()
const {source, target, meta, dispatch} = this.props
if (!source || !target) return
switch (`${source.type}_to_${target.type}`) {
case 'TAB_to_PANE':
switch (`${source.type}_on_${target.type}`) {
case 'TAB_on_PANE':
dispatch(PaneActions.splitTo(target.id, meta.paneSplitDirection))
.then(newPane => {
let tabGroupId = newPane.views[0]
dispatch(TabActions.moveTabToGroup(source.id, tabGroupId))
})
break

case 'TAB_on_TABBAR':
dispatch(TabActions.moveTabToGroup(source.id, target.id.replace('tab_bar_', '')))
break

case 'TAB_on_TABLABEL':
dispatch(TabActions.insertTabAt(source.id, target.id.replace('tab_label_', '')))
break

default:

}
dispatch(dragEnd())
}
Expand All @@ -90,6 +106,16 @@ class DragAndDrop extends Component {
dispatch(dragEnd())
}

dragTabOverTabBar (e, target) {
const {dispatch} = this.props
if (target.type !== 'TABLABEL' && target.type !== 'TABBAR') return
if (target.type === 'TABLABEL') {
dispatch(updateDragOverMeta({tabLabelTargetId: target.id}))
} else {
dispatch(updateDragOverMeta({tabBarTargetId: target.id}))
}
}

dragTabOverPane (e, target) {
if (target.type !== 'PANE') return
const {meta, dispatch} = this.props
Expand Down
4 changes: 2 additions & 2 deletions app/components/Pane/PaneAxis.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
import cx from 'classnames'
import * as PaneActions from './actions'
import TabViewContainer from '../Tab'
import TabContainer from '../Tab'
import AceEditor from '../AceEditor'


Expand All @@ -24,7 +24,7 @@ class Pane extends Component {
var tabGroupId = views[0]
content = (
<div className='pane'>
<TabViewContainer defaultContentClass={AceEditor} defaultContentType='editor' tabGroupId={tabGroupId}/>
<TabContainer defaultContentClass={AceEditor} defaultContentType='editor' tabGroupId={tabGroupId}/>
</div>
)
} else {
Expand Down
12 changes: 6 additions & 6 deletions app/components/Tab/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ export const TAB_REMOVE_GROUP = 'TAB_REMOVE_GROUP'
export const removeGroup = createAction(TAB_REMOVE_GROUP, groupId => groupId)

export const TAB_UPDATE = 'TAB_UPDATE'
export function updateTab(tabConfig) {
return {
type: TAB_UPDATE,
payload: tabConfig
}
}
export const updateTab = createAction(TAB_UPDATE, tabConfig => tabConfig)

export const TAB_UPDATE_FLAGS = 'TAB_UPDATE_FLAGS'
export const updateTabFlags = (tabId, flag, value=true) => {
Expand All @@ -52,3 +47,8 @@ export const TAB_MOVE_TO_GROUP = 'TAB_MOVE_TO_GROUP'
export const moveTabToGroup = createAction(TAB_MOVE_TO_GROUP,
(tabId, groupId) => ({tabId, groupId})
)

export const TAB_INSERT_AT = 'TAB_INSERT_AT'
export const insertTabAt = createAction(TAB_INSERT_AT,
(tabId, beforeTabId) => ({tabId, beforeTabId})
)
44 changes: 31 additions & 13 deletions app/components/Tab/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,40 @@ import * as TabActions from './actions'
import { dragStart } from '../DragAndDrop/actions'
import AceEditor from '../AceEditor'

const TabView = ({getGroupById, groupId, ...otherProps}) => {
const Tab = ({getGroupById, groupId, ...otherProps}) => {
let tabGroup = getGroupById(groupId)
return (
<div className='tab-component'>
<div className='tab-container'>
<TabBar tabs={tabGroup.tabs} groupId={groupId} {...otherProps} />
<TabContent tabs={tabGroup.tabs} groupId={groupId} {...otherProps} />
</div>
)
}

const TabBar = ({tabs, groupId, addTab, ...otherProps}) => {
let TabBar = ({tabs, groupId, addTab, isDraggedOver, ...otherProps}) => {
return (
<div className='tab-bar'>
<ul className='tabs'>
<div className='tab-bar' id={`tab_bar_${groupId}`} data-droppable='TABBAR'>
<ul className='tab-labels'>
{ tabs.map(tab =>
<Tab tab={tab} key={tab.id} {...otherProps} />
<TabLabel tab={tab} key={tab.id} {...otherProps} />
) }
</ul>
{isDraggedOver ? <div className='tab-label-insert-pos'></div>: null}
<div className='tab-add-btn' onClick={e => addTab(groupId)} >+</div>
<div className='tab-show-list'>
<i className='fa fa-sort-desc' />
</div>
</div>
)
}
TabBar = connect((state, ownProps) => ({
isDraggedOver: state.DragAndDrop.meta
? state.DragAndDrop.meta.tabBarTargetId === `tab_bar_${ownProps.groupId}`
: false
})
)(TabBar)

const Tab = ({tab, removeTab, dispatch, activateTab}) => {
let TabLabel = ({tab, isDraggedOver, removeTab, dispatch, activateTab}) => {
const possibleStatus = {
'modified': '*',
'warning': '!',
Expand All @@ -44,14 +51,17 @@ const Tab = ({tab, removeTab, dispatch, activateTab}) => {
}

return (
<li className={cx('tab', {
<li className={cx('tab-label', {
active: tab.isActive,
modified: tab.flags.modified
})}
id={`tab_label_${tab.id}`}
data-droppable='TABLABEL'
onClick={e => activateTab(tab.id)}
draggable='true'
onDragStart={e => dispatch(dragStart({sourceType: 'TAB', sourceId: tab.id}))}
>
{isDraggedOver ? <div className='tab-label-insert-pos'></div>: null}
<div className='title'>{tab.title}</div>
<div className='control'>
<i className='close' onClick={e => { e.stopPropagation(); removeTab(tab.id) }}>×</i>
Expand All @@ -61,6 +71,14 @@ const Tab = ({tab, removeTab, dispatch, activateTab}) => {
)
}

TabLabel = connect((state, ownProps) => ({
isDraggedOver: state.DragAndDrop.meta
? state.DragAndDrop.meta.tabLabelTargetId === `tab_label_${ownProps.tab.id}`
: false
})
)(TabLabel)


const TabContent = ({tabs, defaultContentClass}) => {
return (
<div className='tab-content'>
Expand All @@ -83,7 +101,7 @@ const TabContentItem = ({tab, defaultContentClass}) => {
)
}

class TabViewContainer extends Component {
class TabContainer extends Component {
constructor (props) {
super(props)
this.state = {
Expand All @@ -107,12 +125,12 @@ class TabViewContainer extends Component {

render () {
return (
<TabView {...this.props} groupId={this.state.groupId} />
<Tab {...this.props} groupId={this.state.groupId} />
)
}
}

TabViewContainer = connect(
TabContainer = connect(
state => state.TabState,
dispatch => {
return {
Expand All @@ -122,6 +140,6 @@ TabViewContainer = connect(
dispatch: dispatch
}
}
)(TabViewContainer)
)(TabContainer)

export default TabViewContainer
export default TabContainer
17 changes: 14 additions & 3 deletions app/components/Tab/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
TAB_DISSOLVE_GROUP,
TAB_UPDATE,
TAB_UPDATE_FLAGS,
TAB_MOVE_TO_GROUP
TAB_MOVE_TO_GROUP,
TAB_INSERT_AT
} from './actions'

class Tab {
Expand Down Expand Up @@ -62,7 +63,7 @@ class TabGroup {
this.deactivateAllTabsInGroup()
tab.isActive = true
this.activeTab = tab
if (tab.editor) tab.editor.focus()
if (tab.editor && !tab.editor.isFocused()) tab.editor.focus()
activateGroup(tab.group)
}

Expand Down Expand Up @@ -210,7 +211,6 @@ export default handleActions({
[TAB_MOVE_TO_GROUP]: (state, action) => {
const {tabId, groupId} = action.payload
let tab = getTabById(tabId)
if (tab.group.id === groupId) return state

let tabGroup = getGroupById(groupId)
if (tab.isActive) {
Expand All @@ -220,6 +220,17 @@ export default handleActions({
tabGroup.tabs.push(tab)
tabGroup.activateTab(tab)
return normalizeState(state)
},

[TAB_INSERT_AT]: (state, action) => {
const {tabId, beforeTabId} = action.payload
let sourceTab = getTabById(tabId)
let targetTab = getTabById(beforeTabId)
if (sourceTab.isActive) sourceTab.group.activateNextTab()
sourceTab.group.removeTab(sourceTab)
targetTab.group.tabs.splice(targetTab.group.tabs.indexOf(targetTab), 0, sourceTab)
targetTab.group.activateTab(sourceTab)
return normalizeState(state)
}
}, _state)

Expand Down
4 changes: 2 additions & 2 deletions app/containers/Panels.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import store from '../store.js'
import PanelAxis from '../components/Panel'
import * as PanelActions from '../components/Panel/actions'
import PanesContainer from '../components/Pane'
import TabViewContainer from '../components/Tab'
import TabContainer from '../components/Tab'
import Terminal from '../components/Terminal'
import FileTree from '../components/FileTree'

Expand All @@ -26,7 +26,7 @@ var PanelConfig = {
views: [<PanesContainer />]
}, {
flexDirection: 'row',
views: [<TabViewContainer defaultContentClass={Terminal} defaultContentType='terminal' />],
views: [<TabContainer defaultContentClass={Terminal} defaultContentType='terminal' />],
size: 25
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
$tab-height = 30px;
$tab-control-width = 16px;

.tab-component {
.tab-container {
position: relative;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -37,13 +37,12 @@ $tab-control-width = 16px;
display: flex;
align-items: center;

> ul.tabs {
> ul {
display: flex;
height: 100%;
align-items: flex-end;
overflow-x: scroll;
&::-webkit-scrollbar {display: none;}
> li { margin-right: 2px;}
}

> .tab-show-list {
Expand All @@ -64,14 +63,38 @@ $tab-control-width = 16px;
}
}

.tab {
.tab-label-insert-pos {
width: 2px;
background-color: $blue-highlight-color;
height: 100%;
position: relative;
&:before {
content: '';
display: block;
border-style: solid;
border-color: transparent;
border-width: 6px;
border-bottom: none;
border-top-color: $blue-highlight-color;
absolute(top 4px left -5px)
margin-top: -4px;
}
}

.tab-label {
min-width: 92px;
max-width: 200px;
padding-left: 4px;
flex-grow: 1;
display: flex;
align-items: center;
position: relative;
cursor: default;
border-left: 2px solid transparent;

.tab-label-insert-pos {
absolute(top 0px left -2px bottom 0);
}

.title {
text-overflow: ellipsis;
Expand All @@ -89,7 +112,7 @@ $tab-control-width = 16px;
}


.tab {
.tab-label {
// case default:
i {
absolute(0);
Expand All @@ -104,7 +127,7 @@ $tab-control-width = 16px;
margin-top: $tab-control-width*0.25;
margin-left: $tab-control-width*0.25;
border-radius: 100%;
background-color: #5b91d7;
background-color: $blue-highlight-color;
display: none;
}

Expand All @@ -113,6 +136,10 @@ $tab-control-width = 16px;
display: none;
}

&.active {
border-left-color: $blue-highlight-color
}

// case hover-on-tab / tab-is-active:
&.active, &:hover {
.close {display: block;}
Expand Down
Loading