diff --git a/client/00-startup.js b/client/00-startup.js index 02954353b..b5d0c4d64 100644 --- a/client/00-startup.js +++ b/client/00-startup.js @@ -68,6 +68,7 @@ Meteor.startup(() => { Tracker.autorun(() => { if (Meteor.userId()) { Meteor.subscribe('userGreyIcons'); + Meteor.subscribe('userDesktopDragHandles'); } }); diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 75d9fbd9c..47c6b3f8c 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -124,7 +124,8 @@ template(name='checklistItemDetail') role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0") if canModifyCard span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}} - span.checklistitem-handle(title="{{_ 'dragChecklistItem'}}") ↕️ + if isTouchScreenOrShowDesktopDragHandles + span.checklistitem-handle(title="{{_ 'dragChecklistItem'}}") ↕️ .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title diff --git a/client/components/cards/minicard.css b/client/components/cards/minicard.css index 2ef301d86..171307ef5 100644 --- a/client/components/cards/minicard.css +++ b/client/components/cards/minicard.css @@ -44,10 +44,12 @@ } } .minicard-details-menu-with-handle { - float: right; + position: absolute; + right: 0.7vw; + top: 0.7vh; font-size: clamp(14px, 3vw, 18px); - padding-right: 4vw; - padding-left: 0.7vw; + padding: 0; + z-index: 10; } .minicard-details-menu { float: right; @@ -133,9 +135,10 @@ width: clamp(20px, 2.5vw, 28px); height: clamp(20px, 2.5vw, 28px); position: absolute; - right: 0.7vw; + right: 3vw; top: 0.7vh; display: none; + z-index: 10; } @media only screen { .minicard .handle { diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index ccb546476..e4ddb3af1 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -3,11 +3,12 @@ template(name="minicard") class="{{#if isLinkedCard}}linked-card{{/if}}" class="{{#if isLinkedBoard}}linked-board{{/if}}" class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}") + if canMoveCard + if isTouchScreenOrShowDesktopDragHandles + .handle + | ↕️ if canModifyCard a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") ☰ - if canMoveCard - .handle - | ↕️ .dates if getReceived .date diff --git a/client/components/lists/list.css b/client/components/lists/list.css index 5e916ffcb..10a94f3f0 100644 --- a/client/components/lists/list.css +++ b/client/components/lists/list.css @@ -179,56 +179,42 @@ body.list-resizing-active * { margin-right: 120px !important; } -/* Position drag handle at top-right corner for ALL lists */ -.list-header .list-header-handle { - /* Position at top-right corner, aligned with title text top */ +/* Position elements from right to left: hamburger, add card, drag handle */ +.list-header .js-open-list-menu { position: absolute !important; top: 2.5vh !important; right: 1.5vw !important; - /* Ensure it's above other elements */ z-index: 15 !important; - /* Remove margin since it's absolutely positioned */ - 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 */ -.js-swimlane .list[style*="--list-width"] .list-header .list-header-plus-top, -.js-swimlane .list[style*="--list-width"] .list-header .js-collapse, -.js-swimlane .list[style*="--list-width"] .list-header .js-open-list-menu, -.dragscroll .list[style*="--list-width"] .list-header .list-header-plus-top, -.dragscroll .list[style*="--list-width"] .list-header .js-collapse, -.dragscroll .list[style*="--list-width"] .list-header .js-open-list-menu, -[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-plus-top, -[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-collapse, -[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-open-list-menu { - /* Use original positioning to maintain layout */ - position: relative !important; - /* Maintain original spacing */ - margin-right: 15px !important; - /* Ensure proper display */ +.list-header .list-header-plus-top { + position: absolute !important; + top: 2.5vh !important; + right: 3.25vw !important; + z-index: 15 !important; display: inline-block !important; + padding: 4px !important; } -/* Ensure watch icon and card count maintain original positioning */ -.js-swimlane .list[style*="--list-width"] .list-header .list-header-watch-icon, -.dragscroll .list[style*="--list-width"] .list-header .list-header-watch-icon, -[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-watch-icon, -.js-swimlane .list[style*="--list-width"] .list-header .cardCount, -.dragscroll .list[style*="--list-width"] .list-header .cardCount, -[id^="swimlane-"] .list[style*="--list-width"] .list-header .cardCount { - /* Use original positioning to maintain layout */ - position: relative !important; - /* Maintain original spacing */ - margin-right: 15px !important; - /* Ensure proper display */ +.list-header .list-header-handle-desktop { + position: absolute !important; + top: 2.5vh !important; + right: 6.5vw !important; + z-index: 15 !important; display: inline-block !important; + cursor: move !important; + pointer-events: auto !important; + padding: 4px !important; +} + +/* Anchor header action buttons within header during resize */ +.list .list-header { position: relative; z-index: 5; } +.list .list-header .js-open-list-menu, +.list .list-header .list-header-plus-top, +.list .list-header .list-header-handle-desktop { + position: absolute !important; } [id^="swimlane-"] .list:first-child { min-width: 2.5vw; diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index a3f7e239c..5558ef10f 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -64,12 +64,9 @@ template(name="listHeader") else a.list-header-menu-icon.js-select-list ▶️ unless currentUser.isWorker - a.list-header-handle.handle.js-list-handle ↕️ - else if currentUser.isBoardMember - if currentUser.isBoardMember - unless currentUser.isCommentOnly - unless currentUser.isWorker + if isTouchScreenOrShowDesktopDragHandles a.list-header-handle.handle.js-list-handle ↕️ + else if currentUser.isBoardMember if isWatching i.list-header-watch-icon | 👁️ unless collapsed @@ -77,10 +74,11 @@ template(name="listHeader") unless currentUser.isCommentOnly //if isBoardAdmin // a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}") + if isTouchScreenOrShowDesktopDragHandles + a.list-header-handle-desktop.handle.js-list-handle(title="{{_ 'drag-list'}}") ↕️ if canSeeAddCard a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕ - - a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰ + a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰ template(name="editListTitleForm") .list-composer diff --git a/client/components/main/header.jade b/client/components/main/header.jade index e32399d00..d4a0da8ae 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -30,6 +30,14 @@ template(name="header") span.zoom-display {{zoomLevel}}% input.zoom-input.js-zoom-input(type="number" value=zoomLevel min="50" max="300" step="10" style="display: none;") + // Drag handles toggle - between zoom and mobile mode toggle + a.board-header-btn.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}") + | ↕️ + if isShowDesktopDragHandles + | ✅ + unless isShowDesktopDragHandles + | 🚫 + if isMiniScreen ul.header-quick-access-list if currentList @@ -44,12 +52,6 @@ template(name="header") a(href="{{pathFor 'board' id=_id slug=slug}}") +viewer = title - //a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}") - // i.fa.fa-arrows - // if isShowDesktopDragHandles - // i.fa.fa-check-square-o - // unless isShowDesktopDragHandles - // i.fa.fa-ban #header-new-board-icon else ul.header-quick-access-list @@ -64,12 +66,7 @@ template(name="header") = title else li.current.empty {{_ 'quick-access-description'}} - //a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}") - // i.fa.fa-arrows - // if isShowDesktopDragHandles - // i.fa.fa-check-square-o - // unless isShowDesktopDragHandles - // i.fa.fa-ban + #header-new-board-icon // Next line is used only for spacing at header, // there is no visible clickable icon. #header-new-board-icon diff --git a/client/components/main/header.js b/client/components/main/header.js index 1b9d1deb9..a0c451f4b 100644 --- a/client/components/main/header.js +++ b/client/components/main/header.js @@ -132,11 +132,10 @@ Template.header.events({ Session.set('currentCard', null); }, 'click .js-toggle-desktop-drag-handles'() { - //currentUser = Meteor.user(); - //if (currentUser) { - // Meteor.call('toggleDesktopDragHandles'); - //} else if (window.localStorage.getItem('showDesktopDragHandles')) { - if (window.localStorage.getItem('showDesktopDragHandles')) { + currentUser = Meteor.user(); + if (currentUser) { + Meteor.call('toggleDesktopDragHandles'); + } else if (window.localStorage.getItem('showDesktopDragHandles')) { window.localStorage.removeItem('showDesktopDragHandles'); location.reload(); } else { diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 4fb717463..4f9105e69 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -33,12 +33,13 @@ template(name="swimlaneFixedHeader") | 🔽 a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") | ➕ - unless isTouchScreen - a.swimlane-header-handle.handle.js-swimlane-header-handle - | ↕️ - if isTouchScreen - a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle - | ↕️ + if isTouchScreenOrShowDesktopDragHandles + unless isTouchScreen + a.swimlane-header-handle.handle.js-swimlane-header-handle + | ↕️ + if isTouchScreen + a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle + | ↕️ a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}") | ☰ diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index 49724e84b..1b9411085 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -182,12 +182,12 @@ template(name="changeLanguagePopup") template(name="changeSettingsPopup") ul.pop-over-list - //li - // a.js-toggle-desktop-drag-handles - // i.fa.fa-arrows - // | {{_ 'show-desktop-drag-handles'}} - // if isShowDesktopDragHandles - // i.fa.fa-check + li + a.js-toggle-desktop-drag-handles + | ↕️ + | {{_ 'show-desktop-drag-handles'}} + if isShowDesktopDragHandles + | ✅ unless currentUser.isWorker li label.bold.clear diff --git a/client/lib/utils.js b/client/lib/utils.js index 811e4f180..fbcba009a 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -648,14 +648,18 @@ Utils = { // returns if desktop drag handles are enabled isShowDesktopDragHandles() { - // Always show drag handles on all displays - return true; + const currentUser = Meteor.user(); + if (currentUser) { + return currentUser.hasShowDesktopDragHandles(); + } else { + // For non-logged-in users, check localStorage + return window.localStorage.getItem('showDesktopDragHandles') === 'true'; + } }, // returns if mini screen or desktop drag handles isTouchScreenOrShowDesktopDragHandles() { - // Always enable drag handles for all displays - return true; + return Utils.isTouchScreen() || Utils.isShowDesktopDragHandles(); }, calculateIndexData(prevData, nextData, nItems = 1) { diff --git a/server/publications/userDesktopDragHandles.js b/server/publications/userDesktopDragHandles.js new file mode 100644 index 000000000..3603ecaf6 --- /dev/null +++ b/server/publications/userDesktopDragHandles.js @@ -0,0 +1,8 @@ +Meteor.publish('userDesktopDragHandles', function() { + if (!this.userId) return this.ready(); + return Meteor.users.find({ _id: this.userId }, { + fields: { + 'profile.showDesktopDragHandles': 1 + } + }); +}); \ No newline at end of file