Browse Source

Fix drag drop lists. Part 1.

Thanks to xet7 !

Related #5951
Lauri Ojansivu 2 days ago
parent
commit
324f3f7794

+ 13 - 15
client/components/boards/boardBody.js

@@ -550,22 +550,20 @@ BlazeComponent.extendComponent({
       // Always reset dragscroll on view switch
       dragscroll.reset();
 
-      if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
-        $swimlanesDom.sortable({
-          handle: '.js-swimlane-header-handle',
-        });
-      } else {
-        $swimlanesDom.sortable({
-          handle: '.swimlane-header',
-        });
-      }
+      if ($swimlanesDom.data('uiSortable') || $swimlanesDom.data('sortable')) {
+        if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
+          $swimlanesDom.sortable('option', 'handle', '.js-swimlane-header-handle');
+        } else {
+          $swimlanesDom.sortable('option', 'handle', '.swimlane-header');
+        }
 
-      // Disable drag-dropping if the current user is not a board member
-      $swimlanesDom.sortable(
-        'option',
-        'disabled',
-        !ReactiveCache.getCurrentUser()?.isBoardAdmin(),
-      );
+        // Disable drag-dropping if the current user is not a board member
+        $swimlanesDom.sortable(
+          'option',
+          'disabled',
+          !ReactiveCache.getCurrentUser()?.isBoardAdmin(),
+        );
+      }
     });
 
     // If there is no data in the board (ie, no lists) we autofocus the list

+ 6 - 10
client/components/boards/boardsList.jade

@@ -64,11 +64,9 @@ template(name="boardList")
                     i.fa.js-has-spenttime-cards(
                       class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
                       title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
-                  if isTouchScreenOrShowDesktopDragHandles
-                    i.fa.board-handle(
-                        class="fa-arrows"
-                        title="{{_ 'drag-board'}}")
-                  else
+                  i.fa.board-handle(
+                      class="fa-arrows"
+                      title="{{_ 'drag-board'}}")
                     if isSandstorm
                       i.fa.js-clone-board(
                           class="fa-clone"
@@ -119,11 +117,9 @@ template(name="boardList")
                     i.fa.js-has-spenttime-cards(
                       class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
                       title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
-                  if isTouchScreenOrShowDesktopDragHandles
-                    i.fa.board-handle(
-                        class="fa-arrows"
-                        title="{{_ 'drag-board'}}")
-                  else
+                  i.fa.board-handle(
+                      class="fa-arrows"
+                      title="{{_ 'drag-board'}}")
                     if isSandstorm
                       a.js-clone-board(
                         class="fa-clone"

+ 1 - 2
client/components/cards/checklists.jade

@@ -125,8 +125,7 @@ template(name='checklistItemDetail')
     if canModifyCard
       .check-box-container
         .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
-      if isTouchScreenOrShowDesktopDragHandles
-        span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
+      span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
       .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
         +viewer
           = item.title

+ 3 - 6
client/components/cards/minicard.jade

@@ -4,12 +4,9 @@ template(name="minicard")
     class="{{#if isLinkedBoard}}linked-board{{/if}}"
     class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
     if canModifyCard
-      if isTouchScreenOrShowDesktopDragHandles
-        a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰
-        .handle
-          | ↔️
-      else
-        a.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰
+      a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰
+      .handle
+        | ↕️
     .dates
       if getReceived
         .date

+ 5 - 0
client/components/lists/list.css

@@ -191,6 +191,11 @@ body.list-resizing-active * {
   margin-right: 0 !important;
   /* Ensure proper display */
   display: inline-block !important;
+  /* Ensure it's clickable and shows proper cursor */
+  cursor: move !important;
+  pointer-events: auto !important;
+  /* Add some padding for better clickability */
+  padding: 4px !important;
 }
 
 /* Ensure buttons maintain original positioning */

+ 6 - 19
client/components/lists/list.js

@@ -150,26 +150,13 @@ BlazeComponent.extendComponent({
     });
 
     this.autorun(() => {
-      if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
-        $cards.sortable({
-          handle: '.handle',
-        });
-      } else {
-        $cards.sortable({
-          handle: '.minicard',
-          // Prevent sortable from interfering with file drag and drop
-          start: function(event, ui) {
-            // Check if this is a file drag operation
-            const dataTransfer = event.originalEvent?.dataTransfer;
-            if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
-              // Cancel sortable for file drags
-              return false;
-            }
-          },
-        });
-      }
-
       if ($cards.data('uiSortable') || $cards.data('sortable')) {
+        if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
+          $cards.sortable('option', 'handle', '.handle');
+        } else {
+          $cards.sortable('option', 'handle', '.minicard');
+        }
+
         $cards.sortable(
           'option',
           'disabled',

+ 3 - 3
client/components/lists/listHeader.jade

@@ -70,9 +70,9 @@ template(name="listHeader")
                   | ⬅️
                   | ➡️
                 a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") | ☰
-            if currentUser.isBoardAdmin
-              if isTouchScreenOrShowDesktopDragHandles
-                a.list-header-handle.handle.js-list-handle | 
+            if currentUser.isBoardMember
+              unless currentUser.isCommentOnly
+                a.list-header-handle.handle.js-list-handle | 
 
 template(name="editListTitleForm")
   .list-composer

+ 2 - 3
client/components/swimlanes/swimlaneHeader.jade

@@ -38,9 +38,8 @@ template(name="swimlaneFixedHeader")
       //    ⬆️.swimlane-header-collapse-up
       //    i.fa.fa-arrow-down.swimlane-header-collapse-down
       unless isTouchScreen
-        if isShowDesktopDragHandles
-          a.swimlane-header-handle.handle.js-swimlane-header-handle
-            | ↕️
+        a.swimlane-header-handle.handle.js-swimlane-header-handle
+          | ↕️
       if isTouchScreen
         a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
           | ↕️

+ 165 - 29
client/components/swimlanes/swimlanes.js

@@ -1,4 +1,5 @@
 import { ReactiveCache } from '/imports/reactiveCache';
+import dragscroll from '@wekanteam/dragscroll';
 const { calculateIndex } = Utils;
 
 function currentListIsInThisSwimlane(swimlaneId) {
@@ -43,6 +44,20 @@ function currentCardIsInThisList(listId, swimlaneId) {
 }
 
 function initSortable(boardComponent, $listsDom) {
+  // Safety check: ensure we have valid DOM elements
+  if (!$listsDom || $listsDom.length === 0) {
+    console.error('initSortable: No valid DOM elements provided');
+    return;
+  }
+  
+  // Check if sortable is already initialized
+  if ($listsDom.data('uiSortable') || $listsDom.data('sortable')) {
+    console.log('initSortable: Sortable already initialized, skipping');
+    return;
+  }
+  
+  console.log('initSortable: Initializing sortable for', $listsDom.length, 'elements');
+  
   // We want to animate the card details window closing. We rely on CSS
   // transition for the actual animation.
   $listsDom._uihooks = {
@@ -62,20 +77,74 @@ function initSortable(boardComponent, $listsDom) {
     },
   };
 
-  $listsDom.sortable({
-    connectWith: '.js-swimlane, .js-lists',
-    tolerance: 'pointer',
-    helper: 'clone',
-    items: '.js-list:not(.js-list-composer)',
-    placeholder: 'js-list placeholder',
-    distance: 7,
+  console.log('Initializing list sortable with', $listsDom.length, 'elements');
+  console.log('Connected elements:', $('.js-swimlane, .js-lists').length);
+  console.log('Available swimlanes:', $('.js-swimlane').map(function() { return $(this).attr('id'); }).get());
+  
+  // Add click debugging for drag handles
+  $listsDom.on('mousedown', '.js-list-handle', function(e) {
+    console.log('List drag handle clicked!', e.target);
+    e.stopPropagation();
+  });
+  
+  $listsDom.on('mousedown', '.js-list-header', function(e) {
+    console.log('List header clicked!', e.target);
+  });
+  
+  // Add debugging for sortable events
+  $listsDom.on('sortstart', function(e, ui) {
+    console.log('Sortable start event fired!', ui.item);
+  });
+  
+  $listsDom.on('sortbeforestop', function(e, ui) {
+    console.log('Sortable beforeStop event fired!', ui.item);
+  });
+  
+  $listsDom.on('sortstop', function(e, ui) {
+    console.log('Sortable stop event fired!', ui.item);
+  });
+  
+  try {
+    $listsDom.sortable({
+      connectWith: '.js-swimlane, .js-lists',
+      tolerance: 'pointer',
+      appendTo: '.board-canvas',
+      helper(evt, item) {
+        const helper = item.clone();
+        helper.css('z-index', 1000);
+        return helper;
+      },
+      items: '.js-list:not(.js-list-composer)',
+      placeholder: 'list placeholder',
+      distance: 3,
+      forcePlaceholderSize: true,
+      cursor: 'move',
     start(evt, ui) {
+      console.log('List drag started');
+      ui.helper.css('z-index', 1000);
       ui.placeholder.height(ui.helper.height());
       ui.placeholder.width(ui.helper.width());
       EscapeActions.executeUpTo('popup-close');
       boardComponent.setIsDragging(true);
+      
+      // Add visual feedback for list being dragged
+      ui.item.addClass('ui-sortable-helper');
+      
+      // Disable dragscroll during list dragging to prevent interference
+      console.log('Disabling dragscroll for list dragging');
+      dragscroll.reset();
+      
+      // Also disable dragscroll on all swimlanes during list dragging
+      $('.js-swimlane').each(function() {
+        $(this).removeClass('dragscroll');
+      });
+    },
+    beforeStop(evt, ui) {
+      // Clean up visual feedback
+      ui.item.removeClass('ui-sortable-helper');
     },
     stop(evt, ui) {
+      console.log('List drag stopped');
       // To attribute the new index number, we need to get the DOM element
       // of the previous and the following card -- if any.
       const prevListDom = ui.item.prev('.js-list').get(0);
@@ -83,15 +152,39 @@ function initSortable(boardComponent, $listsDom) {
       const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
 
       const listDomElement = ui.item.get(0);
-      const list = Blaze.getData(listDomElement);
+      if (!listDomElement) {
+        console.error('List DOM element not found during drag stop');
+        return;
+      }
+      
+      let list;
+      try {
+        list = Blaze.getData(listDomElement);
+      } catch (error) {
+        console.error('Error getting list data:', error);
+        return;
+      }
+      
+      if (!list) {
+        console.error('List data not found for element:', listDomElement);
+        return;
+      }
 
       // Detect if the list was dropped in a different swimlane
       const targetSwimlaneDom = ui.item.closest('.js-swimlane');
       let targetSwimlaneId = null;
 
+      console.log('Target swimlane DOM:', targetSwimlaneDom.length, targetSwimlaneDom.attr('id'));
+
       if (targetSwimlaneDom.length > 0) {
         // List was dropped in a swimlane
-        targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
+        try {
+          targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
+          console.log('List dropped in swimlane:', targetSwimlaneId);
+        } catch (error) {
+          console.error('Error getting target swimlane ID:', error);
+          return;
+        }
       } else {
         // List was dropped in lists view (not swimlanes view)
         // In this case, assign to the default swimlane
@@ -100,6 +193,7 @@ function initSortable(boardComponent, $listsDom) {
           const defaultSwimlane = currentBoard.getDefaultSwimline();
           if (defaultSwimlane) {
             targetSwimlaneId = defaultSwimlane._id;
+            console.log('List dropped in lists view, using default swimlane:', targetSwimlaneId);
           }
         }
       }
@@ -123,6 +217,11 @@ function initSortable(boardComponent, $listsDom) {
 
       // Check if the list was dropped in a different swimlane
       const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;
+      console.log('Cross-swimlane check:', {
+        originalSwimlaneId,
+        targetSwimlaneId,
+        isDifferentSwimlane
+      });
 
       // If the list was dropped in a different swimlane, update the swimlaneId
       if (isDifferentSwimlane) {
@@ -152,34 +251,63 @@ function initSortable(boardComponent, $listsDom) {
         $listsDom.sortable('cancel');
       }
 
-      Lists.update(list._id, {
-        $set: updateData,
-      });
+      try {
+        Lists.update(list._id, {
+          $set: updateData,
+        });
+        console.log('List updated successfully:', list._id, updateData);
+      } catch (error) {
+        console.error('Error updating list:', error);
+        return;
+      }
 
       boardComponent.setIsDragging(false);
+      
+      // Re-enable dragscroll after list dragging is complete
+      console.log('Re-enabling dragscroll after list dragging');
+      dragscroll.reset();
+      
+      // Re-enable dragscroll on all swimlanes
+      $('.js-swimlane').each(function() {
+        $(this).addClass('dragscroll');
+      });
     },
   });
+  } catch (error) {
+    console.error('Error initializing list sortable:', error);
+    return;
+  }
+  
+  console.log('List sortable initialization completed successfully');
+  console.log('Sortable data after init:', $listsDom.data('uiSortable') || $listsDom.data('sortable'));
 
   boardComponent.autorun(() => {
-    if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
-      $listsDom.sortable({
-        handle: '.js-list-handle',
-        connectWith: '.js-swimlane, .js-lists',
-      });
-    } else {
-      $listsDom.sortable({
-        handle: '.js-list-header',
-        connectWith: '.js-swimlane, .js-lists',
-      });
-    }
-
     const $listDom = $listsDom;
+    console.log('Autorun running, checking sortable status...');
+    console.log('Has uiSortable:', $listDom.data('uiSortable'));
+    console.log('Has sortable:', $listDom.data('sortable'));
     if ($listDom.data('uiSortable') || $listDom.data('sortable')) {
-      $listsDom.sortable(
-        'option',
-        'disabled',
-        !ReactiveCache.getCurrentUser()?.isBoardAdmin(),
-      );
+      if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
+        $listsDom.sortable('option', 'handle', '.js-list-handle');
+        console.log('List sortable: Using .js-list-handle as handle');
+        console.log('Found drag handles:', $listsDom.find('.js-list-handle').length);
+      } else {
+        $listsDom.sortable('option', 'handle', '.js-list-header');
+        console.log('List sortable: Using .js-list-header as handle');
+        console.log('Found list headers:', $listsDom.find('.js-list-header').length);
+      }
+      
+      const currentUser = ReactiveCache.getCurrentUser();
+      const canModify = Utils.canModifyBoard();
+      const isDisabled = !canModify;
+      $listsDom.sortable('option', 'disabled', isDisabled);
+      console.log('List sortable disabled:', isDisabled);
+      console.log('Can modify board:', canModify);
+      console.log('Current user:', currentUser ? currentUser.username : 'null');
+      console.log('Is board member:', currentUser ? currentUser.isBoardMember() : 'null');
+      console.log('Is comment only:', currentUser ? currentUser.isCommentOnly() : 'null');
+    } else {
+      console.log('List sortable not initialized yet');
     }
   });
 }
@@ -188,11 +316,15 @@ BlazeComponent.extendComponent({
   onRendered() {
     const boardComponent = this.parentComponent();
     const $listsDom = this.$('.js-lists');
+    
+    console.log('Swimlane onRendered - DOM elements found:', $listsDom.length);
+    console.log('Swimlane onRendered - DOM element:', $listsDom[0]);
 
     if (!Utils.getCurrentCardId()) {
       boardComponent.scrollLeft();
     }
 
+    console.log('Calling initSortable...');
     initSortable(boardComponent, $listsDom);
   },
   onCreated() {
@@ -538,11 +670,15 @@ BlazeComponent.extendComponent({
   onRendered() {
     const boardComponent = this.parentComponent();
     const $listsDom = this.$('.js-lists');
+    
+    console.log('ListsGroup onRendered - DOM elements found:', $listsDom.length);
+    console.log('ListsGroup onRendered - DOM element:', $listsDom[0]);
 
     if (!Utils.getCurrentCardId()) {
       boardComponent.scrollLeft();
     }
 
+    console.log('ListsGroup calling initSortable...');
     initSortable(boardComponent, $listsDom);
   },
 }).register('listsGroup');

+ 4 - 20
client/lib/utils.js

@@ -496,30 +496,14 @@ Utils = {
 
   // returns if desktop drag handles are enabled
   isShowDesktopDragHandles() {
-    if (this.isTouchScreen()) {
-      return true;
-    /*
-    const currentUser = ReactiveCache.getCurrentUser();
-    if (currentUser) {
-      return (currentUser.profile || {}).showDesktopDragHandles;
-    } else if (window.localStorage.getItem('showDesktopDragHandles')) {
-    //
-    if (window.localStorage.getItem('showDesktopDragHandles')) {
-      return true;
-    } else {
-      return false;
-    */
-    } else {
-      return false;
-    }
+    // Always show drag handles on all displays
+    return true;
   },
 
   // returns if mini screen or desktop drag handles
   isTouchScreenOrShowDesktopDragHandles() {
-    // Always enable drag handles for mobile screens (touch devices)
-    return this.isTouchScreen() || this.isMiniScreen();
-    //return this.isTouchScreen() || this.isShowDesktopDragHandles();
-    //return this.isShowDesktopDragHandles();
+    // Always enable drag handles for all displays
+    return true;
   },
 
   calculateIndexData(prevData, nextData, nItems = 1) {

+ 2 - 2
packages/wekan-fullcalendar/fullcalendar/fullcalendar.js

@@ -2509,14 +2509,14 @@ var GlobalEmitter = /** @class */ (function () {
         // http://stackoverflow.com/a/32954565/96342
         window.addEventListener('scroll', this.handleScrollProxy = function (ev) {
             _this.handleScroll($.Event(ev));
-        }, true // useCapture
+        }, { passive: true, capture: true } // useCapture with passive for better performance
         );
     };
     GlobalEmitter.prototype.unbind = function () {
         this.stopListeningTo($(document));
         window.removeEventListener('touchmove', this.handleTouchMoveProxy, { passive: false } // use same options as addEventListener
         );
-        window.removeEventListener('scroll', this.handleScrollProxy, true // useCapture
+        window.removeEventListener('scroll', this.handleScrollProxy, { passive: true, capture: true } // use same options as addEventListener
         );
     };
     // Touch Handlers