From 7cc34c2c286053ffdbd0d3a2064402e979c52ccc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:02:06 +0000 Subject: [PATCH 1/6] Initial plan From cfde5b5b2ce63f2221fefdef7240876396a3ce6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:07:20 +0000 Subject: [PATCH 2/6] Convert stickers.js to use PointerEvents API Co-authored-by: floe <90756+floe@users.noreply.github.com> --- webclient/stickers.js | 116 ++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/webclient/stickers.js b/webclient/stickers.js index b19b1f3..0b56e2f 100644 --- a/webclient/stickers.js +++ b/webclient/stickers.js @@ -27,31 +27,23 @@ function imagePreviewFunc(that, previewerId) { } } -function getDistanceBetweenTouches(touches) { - var touch1 = touches[0]; - var touch2 = touches[1]; - var dx = touch1.clientX - touch2.clientX; - var dy = touch1.clientY - touch2.clientY; +function getDistanceBetweenPointers(pointers) { + var pointer1 = pointers[0]; + var pointer2 = pointers[1]; + var dx = pointer1.clientX - pointer2.clientX; + var dy = pointer1.clientY - pointer2.clientY; return Math.sqrt(dx*dx + dy*dy); } -function getAngleBetweenTouches(touches) { - var touch1 = touches[0]; - var touch2 = touches[1]; - var dx = touch1.clientX - touch2.clientX; - var dy = touch1.clientY - touch2.clientY; +function getAngleBetweenPointers(pointers) { + var pointer1 = pointers[0]; + var pointer2 = pointers[1]; + var dx = pointer1.clientX - pointer2.clientX; + var dy = pointer1.clientY - pointer2.clientY; return -360*Math.atan2(dy,dx)/(2*Math.PI); } -function touchify(evt) { - if (evt.touches === undefined) { - evt.touches = [ { clientX: evt.clientX, clientY: evt.clientY } ]; - } - //console.log(evt); -} - function move_start(evt) { - touchify(evt); var sticker = evt.target; evt.preventDefault(); @@ -61,15 +53,33 @@ function move_start(evt) { return; } + // Initialize pointers array if it doesn't exist + if (!sticker.activePointers) { + sticker.activePointers = []; + } + + // Add this pointer to the active pointers + sticker.activePointers.push({ + pointerId: evt.pointerId, + clientX: evt.clientX, + clientY: evt.clientY + }); + sticker.isActive = true; - sticker.offset = [ - sticker.offsetLeft - evt.touches[0].clientX, - sticker.offsetTop - evt.touches[0].clientY - ]; - - if (evt.touches.length >= 2) { - sticker.startDistance = getDistanceBetweenTouches(evt.touches); - sticker.startAngle = getAngleBetweenTouches(evt.touches); + + // Set pointer capture for better tracking + try { sticker.setPointerCapture(evt.pointerId); } catch(e) {} + + if (sticker.activePointers.length === 1) { + // Single pointer - setup for dragging + sticker.offset = [ + sticker.offsetLeft - evt.clientX, + sticker.offsetTop - evt.clientY + ]; + } else if (sticker.activePointers.length >= 2) { + // Multiple pointers - setup for pinch/rotate + sticker.startDistance = getDistanceBetweenPointers(sticker.activePointers); + sticker.startAngle = getAngleBetweenPointers(sticker.activePointers); } // move sticker to the top @@ -78,7 +88,27 @@ function move_start(evt) { function move_end(evt) { var sticker = evt.target; - sticker.isActive = false; + + // Release pointer capture + try { sticker.releasePointerCapture(evt.pointerId); } catch(e) {} + + // Remove this pointer from active pointers + if (sticker.activePointers) { + sticker.activePointers = sticker.activePointers.filter(p => p.pointerId !== evt.pointerId); + + // If we still have 2 or more pointers, recalculate start values for remaining pointers + if (sticker.activePointers.length >= 2) { + sticker.startDistance = getDistanceBetweenPointers(sticker.activePointers); + sticker.startAngle = getAngleBetweenPointers(sticker.activePointers); + } + + // If no more pointers, deactivate + if (sticker.activePointers.length === 0) { + sticker.isActive = false; + } + } else { + sticker.isActive = false; + } } function wheel(evt) { @@ -90,24 +120,34 @@ function wheel(evt) { } function do_move(evt) { - touchify(evt); var sticker = evt.target; if (!sticker.isActive) return; evt.preventDefault(); - sticker.style.left = (evt.touches[0].clientX + sticker.offset[0]) + 'px'; - sticker.style.top = (evt.touches[0].clientY + sticker.offset[1]) + 'px'; + // Update the position of this pointer in the activePointers array + if (sticker.activePointers) { + var pointerIndex = sticker.activePointers.findIndex(p => p.pointerId === evt.pointerId); + if (pointerIndex !== -1) { + sticker.activePointers[pointerIndex].clientX = evt.clientX; + sticker.activePointers[pointerIndex].clientY = evt.clientY; + } + } - if (evt.touches.length >= 2) { - var currentDistance = getDistanceBetweenTouches(evt.touches); + if (sticker.activePointers && sticker.activePointers.length >= 2) { + // Multi-pointer gesture: scale and rotate + var currentDistance = getDistanceBetweenPointers(sticker.activePointers); var newScale = currentDistance / sticker.startDistance; sticker.startDistance = currentDistance; setStickerScale(sticker, sticker.curScale * newScale); - var currentAngle = getAngleBetweenTouches(evt.touches); + var currentAngle = getAngleBetweenPointers(sticker.activePointers); var deltaAngle = sticker.startAngle - currentAngle; sticker.startAngle = currentAngle; setStickerRotation(sticker, sticker.curAngle + deltaAngle); + } else { + // Single pointer: drag + sticker.style.left = (evt.clientX + sticker.offset[0]) + 'px'; + sticker.style.top = (evt.clientY + sticker.offset[1]) + 'px'; } } @@ -138,12 +178,10 @@ function add_sticker(elem) { sticker.curAngle = 0; sticker.curScale = 1; - sticker.addEventListener("touchstart", move_start); - sticker.addEventListener("mousedown", move_start); - sticker.addEventListener("touchend", move_end); - sticker.addEventListener("mouseup", move_end); - sticker.addEventListener("touchmove", do_move); - sticker.addEventListener("mousemove", do_move); + sticker.addEventListener("pointerdown", move_start); + sticker.addEventListener("pointerup", move_end); + sticker.addEventListener("pointermove", do_move); + sticker.addEventListener("pointercancel", move_end); // Handle pointer cancel (e.g., when pointer is lost) sticker.addEventListener("wheel", wheel); sticker.src = elem.src; From 1f81057ed65d344ecfe0204418cc04c632d8af68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:10:48 +0000 Subject: [PATCH 3/6] Add safety checks for pointer tracking edge cases Co-authored-by: floe <90756+floe@users.noreply.github.com> --- webclient/stickers.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webclient/stickers.js b/webclient/stickers.js index 0b56e2f..8fa14cc 100644 --- a/webclient/stickers.js +++ b/webclient/stickers.js @@ -130,12 +130,19 @@ function do_move(evt) { if (pointerIndex !== -1) { sticker.activePointers[pointerIndex].clientX = evt.clientX; sticker.activePointers[pointerIndex].clientY = evt.clientY; + } else { + // Pointer not found - possibly a race condition, ignore this event + return; } } if (sticker.activePointers && sticker.activePointers.length >= 2) { // Multi-pointer gesture: scale and rotate var currentDistance = getDistanceBetweenPointers(sticker.activePointers); + + // Prevent division by zero if pointers start at the same position + if (sticker.startDistance === 0 || currentDistance === 0) return; + var newScale = currentDistance / sticker.startDistance; sticker.startDistance = currentDistance; setStickerScale(sticker, sticker.curScale * newScale); From deab7f4f145b8b80086db3efdc1bac47ab8a47dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:12:37 +0000 Subject: [PATCH 4/6] Fix edge cases and typo from code review feedback Co-authored-by: floe <90756+floe@users.noreply.github.com> --- webclient/stickers.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/webclient/stickers.js b/webclient/stickers.js index 8fa14cc..b2bc6fc 100644 --- a/webclient/stickers.js +++ b/webclient/stickers.js @@ -124,24 +124,35 @@ function do_move(evt) { if (!sticker.isActive) return; evt.preventDefault(); + // Ensure activePointers array exists + if (!sticker.activePointers) return; + // Update the position of this pointer in the activePointers array - if (sticker.activePointers) { - var pointerIndex = sticker.activePointers.findIndex(p => p.pointerId === evt.pointerId); - if (pointerIndex !== -1) { - sticker.activePointers[pointerIndex].clientX = evt.clientX; - sticker.activePointers[pointerIndex].clientY = evt.clientY; - } else { - // Pointer not found - possibly a race condition, ignore this event - return; - } + var pointerIndex = sticker.activePointers.findIndex(p => p.pointerId === evt.pointerId); + if (pointerIndex !== -1) { + sticker.activePointers[pointerIndex].clientX = evt.clientX; + sticker.activePointers[pointerIndex].clientY = evt.clientY; + } else { + // Pointer not found - possibly a race condition, ignore this event + return; } - if (sticker.activePointers && sticker.activePointers.length >= 2) { + if (sticker.activePointers.length >= 2) { // Multi-pointer gesture: scale and rotate var currentDistance = getDistanceBetweenPointers(sticker.activePointers); // Prevent division by zero if pointers start at the same position - if (sticker.startDistance === 0 || currentDistance === 0) return; + if (sticker.startDistance === 0) { + // Initialize distance now that pointers have moved + sticker.startDistance = currentDistance; + return; + } + + if (currentDistance === 0) { + // Pointers are overlapping - maintain state but skip scaling + sticker.startDistance = currentDistance; + return; + } var newScale = currentDistance / sticker.startDistance; sticker.startDistance = currentDistance; @@ -195,7 +206,7 @@ function add_sticker(elem) { for (const cl of elem.classList) sticker.classList.add(cl); fakecanvas.append(sticker); - // set intial scale for all stickers based on canvas width + // set initial scale for all stickers based on canvas width sticker.curScale = fakecanvas.offsetWidth / 1280.0; setStickerTransform(sticker); } From 2ecfbe41185264ca2931a66c6a753b495a4016e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:13:38 +0000 Subject: [PATCH 5/6] Add input validation to pointer helper functions Co-authored-by: floe <90756+floe@users.noreply.github.com> --- webclient/stickers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webclient/stickers.js b/webclient/stickers.js index b2bc6fc..8606841 100644 --- a/webclient/stickers.js +++ b/webclient/stickers.js @@ -28,6 +28,7 @@ function imagePreviewFunc(that, previewerId) { } function getDistanceBetweenPointers(pointers) { + if (!pointers || pointers.length < 2) return 0; var pointer1 = pointers[0]; var pointer2 = pointers[1]; var dx = pointer1.clientX - pointer2.clientX; @@ -36,6 +37,7 @@ function getDistanceBetweenPointers(pointers) { } function getAngleBetweenPointers(pointers) { + if (!pointers || pointers.length < 2) return 0; var pointer1 = pointers[0]; var pointer2 = pointers[1]; var dx = pointer1.clientX - pointer2.clientX; From f86fe2f723cb0b685a1bd1fa375855cb2b0728f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:15:05 +0000 Subject: [PATCH 6/6] Fix startDistance preservation when pointers overlap Co-authored-by: floe <90756+floe@users.noreply.github.com> --- webclient/stickers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webclient/stickers.js b/webclient/stickers.js index 8606841..ef96147 100644 --- a/webclient/stickers.js +++ b/webclient/stickers.js @@ -146,13 +146,14 @@ function do_move(evt) { // Prevent division by zero if pointers start at the same position if (sticker.startDistance === 0) { // Initialize distance now that pointers have moved - sticker.startDistance = currentDistance; + if (currentDistance > 0) { + sticker.startDistance = currentDistance; + } return; } if (currentDistance === 0) { - // Pointers are overlapping - maintain state but skip scaling - sticker.startDistance = currentDistance; + // Pointers are overlapping - preserve previous startDistance and skip scaling return; }