Merge pull request #255 from github/rearrange-tabs

Drag and drop tabs
This commit is contained in:
Kevin Sawicki
2013-02-12 08:38:52 -08:00
15 changed files with 375 additions and 59 deletions

View File

@@ -0,0 +1,97 @@
$ = require 'jquery'
SortableList = require 'sortable-list'
Tab = require './tab'
module.exports =
class TabView extends SortableList
@activate: ->
rootView.eachEditor (editor) =>
@prependToEditorPane(editor) if editor.attached
@prependToEditorPane: (editor) ->
if pane = editor.pane()
pane.prepend(new TabView(editor))
@content: ->
@ul class: "tabs #{@viewClass()}"
initialize: (@editor) ->
super
@addTabForEditSession(editSession) for editSession in @editor.editSessions
@setActiveTab(@editor.getActiveEditSessionIndex())
@editor.on 'editor:active-edit-session-changed', (e, editSession, index) => @setActiveTab(index)
@editor.on 'editor:edit-session-added', (e, editSession) => @addTabForEditSession(editSession)
@editor.on 'editor:edit-session-removed', (e, editSession, index) => @removeTabAtIndex(index)
@editor.on 'editor:edit-session-order-changed', (e, editSession, fromIndex, toIndex) =>
fromTab = @find(".tab:eq(#{fromIndex})")
toTab = @find(".tab:eq(#{toIndex})")
fromTab.detach()
if fromIndex < toIndex
fromTab.insertAfter(toTab)
else
fromTab.insertBefore(toTab)
@on 'click', '.tab', (e) =>
@editor.setActiveEditSessionIndex($(e.target).closest('.tab').index())
@editor.focus()
@on 'click', '.tab .close-icon', (e) =>
index = $(e.target).closest('.tab').index()
@editor.destroyEditSessionIndex(index)
false
addTabForEditSession: (editSession) ->
@append(new Tab(editSession, @editor))
setActiveTab: (index) ->
@find(".tab.active").removeClass('active')
@find(".tab:eq(#{index})").addClass('active')
removeTabAtIndex: (index) ->
@find(".tab:eq(#{index})").remove()
containsEditSession: (editor, editSession) ->
for session in editor.editSessions
return true if editSession.getPath() is session.getPath()
shouldAllowDrag: (event) ->
panes = rootView.find('.pane')
!(panes.length == 1 && panes.find('.sortable').length == 1)
onDragStart: (event) =>
super
pane = $(event.target).closest('.pane')
paneIndex = rootView.indexOfPane(pane)
event.originalEvent.dataTransfer.setData 'from-pane-index', paneIndex
onDrop: (event) =>
super
droppedNearTab = @getSortableElement(event)
transfer = event.originalEvent.dataTransfer
previousDraggedTabIndex = transfer.getData 'sortable-index'
fromPaneIndex = ~~transfer.getData 'from-pane-index'
toPaneIndex = rootView.indexOfPane($(event.target).closest('.pane'))
fromPane = $(rootView.find('.pane')[fromPaneIndex])
fromEditor = fromPane.find('.editor').view()
draggedTab = fromPane.find(".#{TabView.viewClass()} .sortable:eq(#{previousDraggedTabIndex})")
return if draggedTab.is(droppedNearTab)
if fromPaneIndex == toPaneIndex
droppedNearTab = @getSortableElement(event)
fromIndex = draggedTab.index()
toIndex = droppedNearTab.index()
toIndex++ if fromIndex > toIndex
fromEditor.moveEditSessionToIndex(fromIndex, toIndex)
fromEditor.focus()
else
toPane = $(rootView.find('.pane')[toPaneIndex])
toEditor = toPane.find('.editor').view()
unless @containsEditSession(toEditor, fromEditor.editSessions[draggedTab.index()])
fromEditor.moveEditSessionToEditor(draggedTab.index(), toEditor, droppedNearTab.index() + 1)
toEditor.focus()

View File

@@ -4,7 +4,7 @@ fs = require 'fs'
module.exports =
class Tab extends View
@content: (editSession) ->
@li class: 'tab', =>
@li class: 'tab sortable', =>
@span class: 'file-name', outlet: 'fileName'
@span class: 'close-icon'

View File

@@ -1,44 +0,0 @@
$ = require 'jquery'
{View} = require 'space-pen'
Tab = require './tab'
module.exports =
class Tabs extends View
@activate: ->
rootView.eachEditor (editor) =>
@prependToEditorPane(rootView, editor) if editor.attached
@prependToEditorPane: (rootView, editor) ->
if pane = editor.pane()
pane.prepend(new Tabs(editor))
@content: ->
@ul class: 'tabs'
initialize: (@editor) ->
for editSession, index in @editor.editSessions
@addTabForEditSession(editSession)
@setActiveTab(@editor.getActiveEditSessionIndex())
@editor.on 'editor:active-edit-session-changed', (e, editSession, index) => @setActiveTab(index)
@editor.on 'editor:edit-session-added', (e, editSession) => @addTabForEditSession(editSession)
@editor.on 'editor:edit-session-removed', (e, editSession, index) => @removeTabAtIndex(index)
@on 'click', '.tab', (e) =>
@editor.setActiveEditSessionIndex($(e.target).closest('.tab').index())
@editor.focus()
@on 'click', '.tab .close-icon', (e) =>
index = $(e.target).closest('.tab').index()
@editor.destroyEditSessionIndex(index)
false
addTabForEditSession: (editSession) ->
@append(new Tab(editSession, @editor))
setActiveTab: (index) ->
@find(".tab.active").removeClass('active')
@find(".tab:eq(#{index})").addClass('active')
removeTabAtIndex: (index) ->
@find(".tab:eq(#{index})").remove()

View File

@@ -1 +1 @@
'main': 'lib/tabs-view'
'main': 'lib/tab-view'

View File

@@ -3,11 +3,11 @@ _ = require 'underscore'
RootView = require 'root-view'
fs = require 'fs'
describe "Tabs", ->
describe "TabView", ->
[editor, buffer, tabs] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
new RootView(require.resolve('fixtures/sample.js'))
rootView.open('sample.txt')
rootView.simulateDomAttachment()
atom.loadPackage("tabs")
@@ -144,3 +144,68 @@ describe "Tabs", ->
expect(tabs.find('.tab:last .file-name').text()).toBe 'sample.js - tmp'
editor.destroyActiveEditSession()
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js'
describe "when an editor:edit-session-order-changed event is triggered", ->
it "updates the order of the tabs to match the new edit session order", ->
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
editor.moveEditSessionToIndex(0, 1)
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
editor.moveEditSessionToIndex(1, 0)
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
describe "dragging and dropping tabs", ->
describe "when a tab is dragged from and dropped onto the same editor", ->
it "moves the edit session, updates the order of the tabs, and focuses the editor", ->
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.js"
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.txt"
sortableElement = [tabs.find('.tab:eq(0)')]
spyOn(tabs, 'getSortableElement').andCallFake -> sortableElement[0]
event = $.Event()
event.target = tabs[0]
event.originalEvent =
dataTransfer:
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
editor.hiddenInput.focusout()
tabs.onDragStart(event)
sortableElement = [tabs.find('.tab:eq(1)')]
tabs.onDrop(event)
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
expect(editor.isFocused).toBeTruthy()
describe "when a tab is dragged from one editor and dropped onto another editor", ->
it "moves the edit session, updates the order of the tabs, and focuses the destination editor", ->
leftTabs = tabs
rightEditor = editor.splitRight()
rightTabs = rootView.find('.tabs:last').view()
sortableElement = [leftTabs.find('.tab:eq(0)')]
spyOn(tabs, 'getSortableElement').andCallFake -> sortableElement[0]
event = $.Event()
event.target = leftTabs
event.originalEvent =
dataTransfer:
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
rightEditor.hiddenInput.focusout()
tabs.onDragStart(event)
event.target = rightTabs
sortableElement = [rightTabs.find('.tab:eq(0)')]
tabs.onDrop(event)
expect(rightTabs.find('.tab:eq(0) .file-name').text()).toBe "sample.txt"
expect(rightTabs.find('.tab:eq(1) .file-name').text()).toBe "sample.js"
expect(rightEditor.isFocused).toBeTruthy()

View File

@@ -1,4 +1,4 @@
'#root-view':
'body':
'meta-\\': 'tree-view:toggle'
'meta-|': 'tree-view:reveal-active-file'