3 Commits
v0.3 ... v1.1

6 changed files with 166 additions and 18 deletions

View File

@@ -6,16 +6,17 @@
* --td-column-gap value, so it is not in this static stylesheet. * --td-column-gap value, so it is not in this static stylesheet.
* *
* Below: click-and-drag horizontal scrolling (added by Asset/js/drag-scroll.js when the * Below: click-and-drag horizontal scrolling (added by Asset/js/drag-scroll.js when the
* "Drag the board background to scroll" setting is enabled). The .td-drag-scroll class is * "Drag the board background to scroll" setting is enabled). The .td-drag-scroll marker is
* set on #board-container by the script, so these rules are inert when disabled. A grab * set on <body> by the script (not on #board-container, which Kanboard replaces on every
* card move), so these rules are inert when disabled and survive board rebuilds. A grab
* cursor advertises the empty background; cards keep the normal cursor since pressing them * cursor advertises the empty background; cards keep the normal cursor since pressing them
* does a card drag, not a pan. * does a card drag, not a pan.
*/ */
#board-container.td-drag-scroll { body.td-drag-scroll #board-container {
cursor: grab; cursor: grab;
} }
#board-container.td-drag-scroll .task-board { body.td-drag-scroll #board-container .task-board {
cursor: default; cursor: default;
} }

119
Asset/js/drag-autoscroll.js Normal file
View File

@@ -0,0 +1,119 @@
/*
* TweakDrag -- auto-scroll the board while dragging a card near a screen edge.
*
* Kanboard drags cards with jQuery UI sortable. The board does not scroll during a drag
* (jQuery UI's autoscroll is unreliable horizontally, and on touch the bundled Touch Punch
* shim suppresses native scrolling while a card is held), so you can only drop onto columns
* already on screen. This scrolls #board-container while a card drag is active and the
* pointer nears the left/right edge, with a smooth continuous speed that grows toward the
* edge -- so you can drag a card to the edge and roll the board to a far column.
*
* It only acts during an active card drag near an edge, so it is harmless when idle. Works
* on desktop (mouse) and mobile (touch, via Touch Punch). No core changes.
*/
(function () {
"use strict";
var EDGE_ZONE = 50; // px from the board edge where auto-scroll begins
var MAX_SPEED = 22; // px per animation frame at the very edge
var EASE = 1; // 1 = linear ramp, 2 = gentle-then-fast
var jq = window.jQuery;
if (!jq) {
return;
}
var container = null;
var dragging = false;
var rafId = 0;
var lastX = 0;
var lastY = 0;
function onMove(e) {
if (e.touches && e.touches.length) {
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
} else {
lastX = e.clientX;
lastY = e.clientY;
}
}
// Distance is px from the edge; returns 0 at the zone border up to MAX_SPEED at the edge.
function speedFor(distance) {
var p = (EDGE_ZONE - distance) / EDGE_ZONE;
if (p <= 0) {
return 0;
}
if (p > 1) {
p = 1;
}
if (EASE !== 1) {
p = Math.pow(p, EASE);
}
return MAX_SPEED * p;
}
function step() {
if (!dragging || !container) {
return;
}
var rect = container.getBoundingClientRect();
var dLeft = lastX - rect.left;
var dRight = rect.right - lastX;
var speed = 0;
if (dLeft < EDGE_ZONE) {
speed = -speedFor(dLeft);
} else if (dRight < EDGE_ZONE) {
speed = speedFor(dRight);
}
if (speed !== 0) {
var before = container.scrollLeft;
container.scrollLeft = before + speed;
// Only when the board actually moved: nudge jQuery UI to recompute drop
// positions so the placeholder follows onto the newly revealed column.
if (container.scrollLeft !== before) {
document.dispatchEvent(new MouseEvent("mousemove", {
clientX: lastX,
clientY: lastY,
bubbles: true
}));
}
}
rafId = window.requestAnimationFrame(step);
}
function startLoop() {
container = document.getElementById("board-container");
if (!container) {
return;
}
dragging = true;
if (!rafId) {
rafId = window.requestAnimationFrame(step);
}
}
function stopLoop() {
dragging = false;
if (rafId) {
window.cancelAnimationFrame(rafId);
rafId = 0;
}
}
// Track the pointer at all times (cheap; only used while dragging).
document.addEventListener("mousemove", onMove, true);
document.addEventListener("touchmove", onMove, true);
document.addEventListener("pointermove", onMove, true);
// A card drag started / stopped. jQuery UI sortable fires these (on touch too, via
// Touch Punch); delegated on the document so board redraws need no rebinding.
jq(document).on("sortstart", startLoop);
jq(document).on("sortstop", stopLoop);
})();

View File

@@ -6,6 +6,13 @@
* pressing a task card, a drag handle, or any interactive control is ignored, so this * pressing a task card, a drag handle, or any interactive control is ignored, so this
* never interferes with Kanboard's own card and column drag-and-drop. * never interferes with Kanboard's own card and column drag-and-drop.
* *
* Resilience note: after a card drag Kanboard rebuilds the board with
* `$("#board-container").replaceWith(data)` (BoardDragAndDrop.refresh), destroying the
* element. So we must NOT bind to #board-container directly: we listen on `document` and
* re-resolve the container on each press, and the grab-cursor marker lives on <body>
* (which is never replaced). Previously the mousedown listener and the cursor class were
* on the container, so background dragging stopped working after every card move.
*
* Loaded only when the "Drag the board background to scroll" setting is enabled. * Loaded only when the "Drag the board background to scroll" setting is enabled.
*/ */
(function () { (function () {
@@ -21,34 +28,40 @@
} }
function init() { function init() {
var container = document.getElementById("board-container"); if (!document.getElementById("board-container") || document.body.getAttribute("data-td-drag") === "1") {
if (!container || container.getAttribute("data-td-drag") === "1") {
return; return;
} }
container.setAttribute("data-td-drag", "1"); // Marker on <body> (stable) enables the grab cursor via CSS, surviving board rebuilds.
container.classList.add("td-drag-scroll"); document.body.setAttribute("data-td-drag", "1");
document.body.classList.add("td-drag-scroll");
var dragging = false; var dragging = false;
var container = null;
var startX = 0; var startX = 0;
var startScrollLeft = 0; var startScrollLeft = 0;
container.addEventListener("mousedown", function (e) { document.addEventListener("mousedown", function (e) {
// Left button only, and only on empty board background. if (e.button !== 0) {
if (e.button !== 0 || isIgnored(e.target)) {
return; return;
} }
// Re-resolve the current container and only act on its empty background.
var c = document.getElementById("board-container");
if (!c || !c.contains(e.target) || isIgnored(e.target)) {
return;
}
container = c;
dragging = true; dragging = true;
startX = e.clientX; startX = e.clientX;
startScrollLeft = container.scrollLeft; startScrollLeft = c.scrollLeft;
container.classList.add("td-grabbing"); document.body.classList.add("td-grabbing");
e.preventDefault(); e.preventDefault();
}); });
document.addEventListener("mousemove", function (e) { document.addEventListener("mousemove", function (e) {
if (!dragging) { if (!dragging || !container) {
return; return;
} }
container.scrollLeft = startScrollLeft - (e.clientX - startX); container.scrollLeft = startScrollLeft - (e.clientX - startX);
@@ -60,7 +73,8 @@
return; return;
} }
dragging = false; dragging = false;
container.classList.remove("td-grabbing"); container = null;
document.body.classList.remove("td-grabbing");
} }
document.addEventListener("mouseup", stop); document.addEventListener("mouseup", stop);

View File

@@ -25,6 +25,12 @@ class Plugin extends Base
'template' => 'plugins/TweakDrag/Asset/js/drag-scroll.js', 'template' => 'plugins/TweakDrag/Asset/js/drag-scroll.js',
)); ));
} }
// Auto-scroll the board while dragging a card near a screen edge. Always loaded;
// it only acts during an active card drag.
$this->hook->on('template:layout:js', array(
'template' => 'plugins/TweakDrag/Asset/js/drag-autoscroll.js',
));
} }
public function getPluginName() public function getPluginName()
@@ -44,7 +50,7 @@ class Plugin extends Base
public function getPluginVersion() public function getPluginVersion()
{ {
return '0.3.0'; return '1.1.0';
} }
public function getPluginHomepage() public function getPluginHomepage()

View File

@@ -9,6 +9,8 @@ A Kanboard plugin that groups board dragging and spacing tweaks, configurable un
apart and to touch. apart and to touch.
- **Mobile drag handle size** -- enlarge the card drag handle Kanboard shows on mobile so - **Mobile drag handle size** -- enlarge the card drag handle Kanboard shows on mobile so
it is easy to grab with a finger. it is easy to grab with a finger.
- **Drag auto-scroll** -- while dragging a card, the board scrolls when the pointer nears a
screen edge, so you can reach a column that is off screen (desktop and mobile).
These options were originally part of the ShrinkVertically plugin and moved here so that These options were originally part of the ShrinkVertically plugin and moved here so that
drag/touch behaviour can grow on its own (planned: mobile drag-to-move cards, and a drag drag/touch behaviour can grow on its own (planned: mobile drag-to-move cards, and a drag
@@ -30,6 +32,12 @@ movement threshold so ending a drag does not open the task) without bloating tha
is hard to grab. FontAwesome icons are sized by `font-size`, so a single rule scales the is hard to grab. FontAwesome icons are sized by `font-size`, so a single rule scales the
handle from the `--td-handle-size` CSS variable. It is inert on desktop, where Kanboard handle from the `--td-handle-size` CSS variable. It is inert on desktop, where Kanboard
keeps the handle hidden. keeps the handle hidden.
- **Drag auto-scroll**: a script (`Asset/js/drag-autoscroll.js`) listens for jQuery UI
sortable's `sortstart`/`sortstop` (which also fire on touch via Kanboard's bundled Touch
Punch) and, while a card drag is active, scrolls `#board-container` when the pointer nears
its left/right edge. Speed ramps up smoothly toward the edge. After each scroll step it
dispatches a synthetic `mousemove` so jQuery UI moves the drop placeholder onto the newly
revealed column. It only acts during an active drag, so it is idle otherwise.
## Requirements ## Requirements

View File

@@ -1 +1 @@
TweakDrag v0.3 TweakDrag v1.1