aboutsummaryrefslogtreecommitdiff
path: root/internal/ui/static/js/touch_handler.js
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <f@miniflux.net> 2023-08-10 19:46:45 -0700
committerGravatar Frédéric Guillot <f@miniflux.net> 2023-08-10 20:29:34 -0700
commit168a870c025bfef6efdeb46e166e79a16093c157 (patch)
tree4d8ab69c7e3ef03a7ade06e7b5e5053429a64c3b /internal/ui/static/js/touch_handler.js
parentc2349032552891745cbbc3d2a9e772845a0239f4 (diff)
downloadv2-168a870c025bfef6efdeb46e166e79a16093c157.tar.gz
v2-168a870c025bfef6efdeb46e166e79a16093c157.tar.zst
v2-168a870c025bfef6efdeb46e166e79a16093c157.zip
Move internal packages to an internal folder
For reference: https://go.dev/doc/go1.4#internalpackages
Diffstat (limited to 'internal/ui/static/js/touch_handler.js')
-rw-r--r--internal/ui/static/js/touch_handler.js187
1 files changed, 187 insertions, 0 deletions
diff --git a/internal/ui/static/js/touch_handler.js b/internal/ui/static/js/touch_handler.js
new file mode 100644
index 00000000..99c1d5b2
--- /dev/null
+++ b/internal/ui/static/js/touch_handler.js
@@ -0,0 +1,187 @@
+class TouchHandler {
+ constructor() {
+ this.reset();
+ }
+
+ reset() {
+ this.touch = {
+ start: { x: -1, y: -1 },
+ move: { x: -1, y: -1 },
+ moved: false,
+ time: 0,
+ element: null
+ };
+ }
+
+ calculateDistance() {
+ if (this.touch.start.x >= -1 && this.touch.move.x >= -1) {
+ let horizontalDistance = Math.abs(this.touch.move.x - this.touch.start.x);
+ let verticalDistance = Math.abs(this.touch.move.y - this.touch.start.y);
+
+ if (horizontalDistance > 30 && verticalDistance < 70 || this.touch.moved) {
+ return this.touch.move.x - this.touch.start.x;
+ }
+ }
+
+ return 0;
+ }
+
+ findElement(element) {
+ if (element.classList.contains("entry-swipe")) {
+ return element;
+ }
+
+ return DomHelper.findParent(element, "entry-swipe");
+ }
+
+ onItemTouchStart(event) {
+ if (event.touches === undefined || event.touches.length !== 1) {
+ return;
+ }
+
+ this.reset();
+ this.touch.start.x = event.touches[0].clientX;
+ this.touch.start.y = event.touches[0].clientY;
+ this.touch.element = this.findElement(event.touches[0].target);
+ this.touch.element.style.transitionDuration = "0s";
+ }
+
+ onItemTouchMove(event) {
+ if (event.touches === undefined || event.touches.length !== 1 || this.element === null) {
+ return;
+ }
+
+ this.touch.move.x = event.touches[0].clientX;
+ this.touch.move.y = event.touches[0].clientY;
+
+ let distance = this.calculateDistance();
+ let absDistance = Math.abs(distance);
+
+ if (absDistance > 0) {
+ this.touch.moved = true;
+
+ let tx = absDistance > 75 ? Math.pow(absDistance - 75, 0.5) + 75 : absDistance;
+
+ if (distance < 0) {
+ tx = -tx;
+ }
+
+ this.touch.element.style.transform = "translateX(" + tx + "px)";
+
+ event.preventDefault();
+ }
+ }
+
+ onItemTouchEnd(event) {
+ if (event.touches === undefined) {
+ return;
+ }
+
+ if (this.touch.element !== null) {
+ let absDistance = Math.abs(this.calculateDistance());
+
+ if (absDistance > 75) {
+ toggleEntryStatus(this.touch.element);
+ }
+
+ if (this.touch.moved) {
+ this.touch.element.style.transitionDuration = "0.15s";
+ this.touch.element.style.transform = "none";
+ }
+ }
+
+ this.reset();
+ }
+
+ onContentTouchStart(event) {
+ if (event.touches === undefined || event.touches.length !== 1) {
+ return;
+ }
+
+ this.reset();
+ this.touch.start.x = event.touches[0].clientX;
+ this.touch.start.y = event.touches[0].clientY;
+ this.touch.time = Date.now();
+ }
+
+ onContentTouchMove(event) {
+ if (event.touches === undefined || event.touches.length !== 1 || this.element === null) {
+ return;
+ }
+
+ this.touch.move.x = event.touches[0].clientX;
+ this.touch.move.y = event.touches[0].clientY;
+ }
+
+ onContentTouchEnd(event) {
+ if (event.touches === undefined) {
+ return;
+ }
+
+ let distance = this.calculateDistance();
+ let absDistance = Math.abs(distance);
+ let now = Date.now();
+
+ if (now - this.touch.time <= 1000 && absDistance > 75) {
+ if (distance > 0) {
+ goToPage("previous");
+ } else {
+ goToPage("next");
+ }
+ }
+
+ this.reset();
+ }
+
+ onTapEnd(event) {
+ if (event.touches === undefined) {
+ return;
+ }
+
+ let now = Date.now();
+
+ if (this.touch.start.x !== -1 && now - this.touch.time <= 200) {
+ let innerWidthHalf = window.innerWidth / 2;
+
+ if (this.touch.start.x >= innerWidthHalf && event.changedTouches[0].clientX >= innerWidthHalf) {
+ goToPage("next");
+ } else if (this.touch.start.x < innerWidthHalf && event.changedTouches[0].clientX < innerWidthHalf) {
+ goToPage("previous");
+ }
+
+ this.reset();
+ } else {
+ this.reset();
+ this.touch.start.x = event.changedTouches[0].clientX;
+ this.touch.time = now;
+ }
+ }
+
+ listen() {
+ let hasPassiveOption = DomHelper.hasPassiveEventListenerOption();
+
+ let elements = document.querySelectorAll(".entry-swipe");
+
+ elements.forEach((element) => {
+ element.addEventListener("touchstart", (e) => this.onItemTouchStart(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", (e) => this.onItemTouchMove(e), hasPassiveOption ? { passive: false } : false);
+ element.addEventListener("touchend", (e) => this.onItemTouchEnd(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ });
+
+ let element = document.querySelector(".entry-content");
+
+ if (element) {
+ if (element.classList.contains("gesture-nav-tap")) {
+ element.addEventListener("touchend", (e) => this.onTapEnd(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ } else if (element.classList.contains("gesture-nav-swipe")) {
+ element.addEventListener("touchstart", (e) => this.onContentTouchStart(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchmove", (e) => this.onContentTouchMove(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchend", (e) => this.onContentTouchEnd(e), hasPassiveOption ? { passive: true } : false);
+ element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
+ }
+ }
+ }
+}