2323/* eslint-disable no-invalid-this */
2424define ( function ( require , exports , module ) {
2525 const MainViewManager = require ( "view/MainViewManager" ) ;
26+ const CommandManager = require ( "command/CommandManager" ) ;
27+ const Commands = require ( "command/Commands" ) ;
2628
2729 /**
2830 * These variables track the drag and drop state of tabs
2931 * draggedTab: The tab that is currently being dragged
3032 * dragOverTab: The tab that is currently being hovered over
3133 * dragIndicator: Visual indicator showing where the tab will be dropped
3234 * scrollInterval: Used for automatic scrolling when dragging near edges
35+ * dragSourcePane: To track which pane the dragged tab originated from
3336 */
3437 let draggedTab = null ;
3538 let dragOverTab = null ;
3639 let dragIndicator = null ;
3740 let scrollInterval = null ;
41+ let dragSourcePane = null ;
3842
3943
4044 /**
@@ -47,11 +51,8 @@ define(function (require, exports, module) {
4751 * @param {String } secondPaneSelector - Selector for the second pane tab bar $("#phoenix-tab-bar-2")
4852 */
4953 function init ( firstPaneSelector , secondPaneSelector ) {
50- // setup both the tab bars
5154 setupDragForTabBar ( firstPaneSelector ) ;
5255 setupDragForTabBar ( secondPaneSelector ) ;
53-
54- // setup the container-level drag events to catch drops in empty areas and enable auto-scroll
5556 setupContainerDrag ( firstPaneSelector ) ;
5657 setupContainerDrag ( secondPaneSelector ) ;
5758
@@ -60,6 +61,9 @@ define(function (require, exports, module) {
6061 dragIndicator = $ ( '<div class="tab-drag-indicator"></div>' ) ;
6162 $ ( 'body' ) . append ( dragIndicator ) ;
6263 }
64+
65+ // add initialization for empty panes
66+ initEmptyPaneDropTargets ( ) ;
6367 }
6468
6569
@@ -161,14 +165,22 @@ define(function (require, exports, module) {
161165 // If dropping on left half, target the first tab; otherwise, target the last tab
162166 const targetTab = onLeftSide ? $tabs . first ( ) [ 0 ] : $tabs . last ( ) [ 0 ] ;
163167
164- // mkae sure that the draggedTab exists and isn't the same as the target
168+ // make sure that the draggedTab exists and isn't the same as the target
165169 if ( draggedTab && targetTab && draggedTab !== targetTab ) {
166170 // check which pane the container belongs to
167171 const isSecondPane = $container . attr ( "id" ) === "phoenix-tab-bar-2" ;
168- const paneId = isSecondPane ? "second-pane" : "first-pane" ;
172+ const targetPaneId = isSecondPane ? "second-pane" : "first-pane" ;
169173 const draggedPath = $ ( draggedTab ) . attr ( "data-path" ) ;
170174 const targetPath = $ ( targetTab ) . attr ( "data-path" ) ;
171- moveWorkingSetItem ( paneId , draggedPath , targetPath , onLeftSide ) ;
175+
176+ // check if we're dropping in a different pane
177+ if ( dragSourcePane !== targetPaneId ) {
178+ // cross-pane drop
179+ moveTabBetweenPanes ( dragSourcePane , targetPaneId , draggedPath , targetPath , onLeftSide ) ;
180+ } else {
181+ // same pane drop
182+ moveWorkingSetItem ( targetPaneId , draggedPath , targetPath , onLeftSide ) ;
183+ }
172184 }
173185 }
174186 } ) ;
@@ -228,6 +240,9 @@ define(function (require, exports, module) {
228240 e . originalEvent . dataTransfer . effectAllowed = 'move' ;
229241 e . originalEvent . dataTransfer . setData ( 'text/html' , this . innerHTML ) ;
230242
243+ // Store which pane this tab came from
244+ dragSourcePane = $ ( this ) . closest ( "#phoenix-tab-bar-2" ) . length > 0 ? "second-pane" : "first-pane" ;
245+
231246 // Add dragging class for styling
232247 $ ( this ) . addClass ( 'dragging' ) ;
233248
@@ -311,7 +326,7 @@ define(function (require, exports, module) {
311326 if ( draggedTab !== this ) {
312327 // Determine which pane the drop target belongs to
313328 const isSecondPane = $ ( this ) . closest ( "#phoenix-tab-bar-2" ) . length > 0 ;
314- const paneId = isSecondPane ? "second-pane" : "first-pane" ;
329+ const targetPaneId = isSecondPane ? "second-pane" : "first-pane" ;
315330 const draggedPath = $ ( draggedTab ) . attr ( "data-path" ) ;
316331 const targetPath = $ ( this ) . attr ( "data-path" ) ;
317332
@@ -321,8 +336,14 @@ define(function (require, exports, module) {
321336 const midPoint = targetRect . left + ( targetRect . width / 2 ) ;
322337 const onLeftSide = mouseX < midPoint ;
323338
324- // Move the tab in the working set
325- moveWorkingSetItem ( paneId , draggedPath , targetPath , onLeftSide ) ;
339+ // Check if dragging between different panes
340+ if ( dragSourcePane !== targetPaneId ) {
341+ // Move the tab between panes
342+ moveTabBetweenPanes ( dragSourcePane , targetPaneId , draggedPath , targetPath , onLeftSide ) ;
343+ } else {
344+ // Move within the same pane
345+ moveWorkingSetItem ( targetPaneId , draggedPath , targetPath , onLeftSide ) ;
346+ }
326347 }
327348 return false ;
328349 }
@@ -339,6 +360,7 @@ define(function (require, exports, module) {
339360 updateDragIndicator ( null ) ;
340361 draggedTab = null ;
341362 dragOverTab = null ;
363+ dragSourcePane = null ;
342364
343365 // Clear scroll interval if it exists
344366 if ( scrollInterval ) {
@@ -418,6 +440,176 @@ define(function (require, exports, module) {
418440 }
419441 }
420442
443+ /**
444+ * Move a tab from one pane to another
445+ * This function handles cross-pane drag and drop operations
446+ *
447+ * @param {String } sourcePaneId - The ID of the source pane ("first-pane" or "second-pane")
448+ * @param {String } targetPaneId - The ID of the target pane ("first-pane" or "second-pane")
449+ * @param {String } draggedPath - Path of the dragged file
450+ * @param {String } targetPath - Path of the drop target file (in the target pane)
451+ * @param {Boolean } beforeTarget - Whether to place before or after the target
452+ */
453+ function moveTabBetweenPanes ( sourcePaneId , targetPaneId , draggedPath , targetPath , beforeTarget ) {
454+ const sourceWorkingSet = MainViewManager . getWorkingSet ( sourcePaneId ) ;
455+ const targetWorkingSet = MainViewManager . getWorkingSet ( targetPaneId ) ;
456+
457+ let draggedIndex = - 1 ;
458+ let targetIndex = - 1 ;
459+ let draggedFile = null ;
460+
461+ // Find the dragged file and its index in the source pane
462+ for ( let i = 0 ; i < sourceWorkingSet . length ; i ++ ) {
463+ if ( sourceWorkingSet [ i ] . fullPath === draggedPath ) {
464+ draggedIndex = i ;
465+ draggedFile = sourceWorkingSet [ i ] ;
466+ break ;
467+ }
468+ }
469+
470+ // Find the target index in the target pane
471+ for ( let i = 0 ; i < targetWorkingSet . length ; i ++ ) {
472+ if ( targetWorkingSet [ i ] . fullPath === targetPath ) {
473+ targetIndex = i ;
474+ break ;
475+ }
476+ }
477+
478+ // Only continue if we found the dragged file
479+ if ( draggedIndex !== - 1 && draggedFile ) {
480+ // Remove the file from source pane
481+ CommandManager . execute (
482+ Commands . FILE_CLOSE ,
483+ { file : draggedFile , paneId : sourcePaneId }
484+ ) ;
485+
486+ // Calculate where to add it in the target pane
487+ let targetInsertIndex ;
488+
489+ if ( targetIndex !== - 1 ) {
490+ // We have a specific target index to aim for
491+ targetInsertIndex = beforeTarget ? targetIndex : targetIndex + 1 ;
492+ } else {
493+ // No specific target, add to end of the working set
494+ targetInsertIndex = targetWorkingSet . length ;
495+ }
496+
497+ // Add to the target pane at the calculated position
498+ MainViewManager . addToWorkingSet ( targetPaneId , draggedFile , targetInsertIndex ) ;
499+
500+ // If the tab was the active one in the source pane,
501+ // make it active in the target pane too
502+ const activeFile = MainViewManager . getCurrentlyViewedFile ( sourcePaneId ) ;
503+ if ( activeFile && activeFile . fullPath === draggedPath ) {
504+ // Open the file in the target pane and make it active
505+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : draggedPath , paneId : targetPaneId } ) ;
506+ }
507+ }
508+ }
509+
510+ /**
511+ * Initialize drop targets for empty panes
512+ * This creates invisible drop zones when a pane has no files and thus no tab bar
513+ */
514+ function initEmptyPaneDropTargets ( ) {
515+ // get the references to the editor holders (these are always present, even when empty)
516+ const $firstPaneHolder = $ ( "#first-pane .pane-content" ) ;
517+ const $secondPaneHolder = $ ( "#second-pane .pane-content" ) ;
518+
519+ // handle the drop events on empty panes
520+ setupEmptyPaneDropTarget ( $firstPaneHolder , "first-pane" ) ;
521+ setupEmptyPaneDropTarget ( $secondPaneHolder , "second-pane" ) ;
522+ }
523+
524+
525+ /**
526+ * sets up the whole pane as a drop target when it has no tabs
527+ *
528+ * @param {jQuery } $paneHolder - The jQuery object for the pane content area
529+ * @param {String } paneId - The ID of the pane ("first-pane" or "second-pane")
530+ */
531+ function setupEmptyPaneDropTarget ( $paneHolder , paneId ) {
532+ // remove if any existing handlers to prevent duplicates
533+ $paneHolder . off ( "dragover dragenter dragleave drop" ) ;
534+
535+ // Handle drag over empty pane
536+ $paneHolder . on ( "dragover dragenter" , function ( e ) {
537+ // we only want to process if this pane is empty (has no tab bar or has hidden tab bar)
538+ const $tabBar = paneId === "first-pane" ? $ ( "#phoenix-tab-bar" ) : $ ( "#phoenix-tab-bar-2" ) ;
539+ const isEmptyPane = ! $tabBar . length || $tabBar . is ( ":hidden" ) || $tabBar . children ( ".tab" ) . length === 0 ;
540+
541+ if ( isEmptyPane && draggedTab ) {
542+ e . preventDefault ( ) ;
543+ e . stopPropagation ( ) ;
544+
545+ // add visual indicator that this is a drop target [refer to Extn-TabBar.less]
546+ $ ( this ) . addClass ( "empty-pane-drop-target" ) ;
547+
548+ // set the drop effect
549+ e . originalEvent . dataTransfer . dropEffect = 'move' ;
550+ }
551+ } ) ;
552+
553+ // handle leaving an empty pane drop target
554+ $paneHolder . on ( "dragleave" , function ( e ) {
555+ $ ( this ) . removeClass ( "empty-pane-drop-target" ) ;
556+ } ) ;
557+
558+ // Handle drop on empty pane
559+ $paneHolder . on ( "drop" , function ( e ) {
560+ const $tabBar = paneId === "first-pane" ? $ ( "#phoenix-tab-bar" ) : $ ( "#phoenix-tab-bar-2" ) ;
561+ const isEmptyPane = ! $tabBar . length || $tabBar . is ( ":hidden" ) || $tabBar . children ( ".tab" ) . length === 0 ;
562+
563+ if ( isEmptyPane && draggedTab ) {
564+ e . preventDefault ( ) ;
565+ e . stopPropagation ( ) ;
566+
567+ // remove the highlight
568+ $ ( this ) . removeClass ( "empty-pane-drop-target" ) ;
569+
570+ // get the dragged file path
571+ const draggedPath = $ ( draggedTab ) . attr ( "data-path" ) ;
572+
573+ // Determine source pane
574+ const sourcePaneId = $ ( draggedTab )
575+ . closest ( "#phoenix-tab-bar-2" ) . length > 0 ? "second-pane" : "first-pane" ;
576+
577+ // we don't want to do anything if dropping in the same pane
578+ if ( sourcePaneId !== paneId ) {
579+ const sourceWorkingSet = MainViewManager . getWorkingSet ( sourcePaneId ) ;
580+ let draggedFile = null ;
581+
582+ // Find the dragged file in the source pane
583+ for ( let i = 0 ; i < sourceWorkingSet . length ; i ++ ) {
584+ if ( sourceWorkingSet [ i ] . fullPath === draggedPath ) {
585+ draggedFile = sourceWorkingSet [ i ] ;
586+ break ;
587+ }
588+ }
589+
590+ if ( draggedFile ) {
591+ // close in the source pane
592+ CommandManager . execute (
593+ Commands . FILE_CLOSE ,
594+ { file : draggedFile , paneId : sourcePaneId }
595+ ) ;
596+
597+ // and open in the target pane
598+ MainViewManager . addToWorkingSet ( paneId , draggedFile ) ;
599+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : draggedPath , paneId : paneId } ) ;
600+ }
601+ }
602+
603+ // reset all drag state stuff
604+ updateDragIndicator ( null ) ;
605+ draggedTab = null ;
606+ dragOverTab = null ;
607+ dragSourcePane = null ;
608+ }
609+ } ) ;
610+ }
611+
612+
421613 module . exports = {
422614 init
423615 } ;
0 commit comments