Enable Android's context menus in network list.
After #4326 Android users could no longer long-touch to bring up the context menu for channels in the network list. Now they can again.
This commit is contained in:
parent
a3a9a2cdd9
commit
393d4fe591
@ -82,15 +82,11 @@ export default {
|
|||||||
this.$root.switchToChannel(this.channel);
|
this.$root.switchToChannel(this.channel);
|
||||||
},
|
},
|
||||||
openContextMenu(event) {
|
openContextMenu(event) {
|
||||||
// events.buttons will be 0 when the event is caused by a long
|
eventbus.emit("contextmenu:channel", {
|
||||||
// touch on Android.
|
event: event,
|
||||||
if (event.buttons !== 0) {
|
channel: this.channel,
|
||||||
eventbus.emit("contextmenu:channel", {
|
network: this.network,
|
||||||
event: event,
|
});
|
||||||
channel: this.channel,
|
|
||||||
network: this.network,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="isOpen"
|
v-if="isOpen"
|
||||||
id="context-menu-container"
|
id="context-menu-container"
|
||||||
|
:class="{passthrough}"
|
||||||
@click="containerClick"
|
@click="containerClick"
|
||||||
@contextmenu.prevent="containerClick"
|
@contextmenu.prevent="containerClick"
|
||||||
@keydown.exact.up.prevent="navigateMenu(-1)"
|
@keydown.exact.up.prevent="navigateMenu(-1)"
|
||||||
@ -49,6 +50,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
passthrough: false,
|
||||||
previousActiveElement: null,
|
previousActiveElement: null,
|
||||||
items: [],
|
items: [],
|
||||||
activeItem: -1,
|
activeItem: -1,
|
||||||
@ -60,18 +62,35 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventbus.on("escapekey", this.close);
|
eventbus.on("escapekey", this.close);
|
||||||
|
eventbus.on("contextmenu:cancel", this.close);
|
||||||
eventbus.on("contextmenu:user", this.openUserContextMenu);
|
eventbus.on("contextmenu:user", this.openUserContextMenu);
|
||||||
eventbus.on("contextmenu:channel", this.openChannelContextMenu);
|
eventbus.on("contextmenu:channel", this.openChannelContextMenu);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
eventbus.off("escapekey", this.close);
|
eventbus.off("escapekey", this.close);
|
||||||
|
eventbus.off("contextmenu:cancel", this.close);
|
||||||
eventbus.off("contextmenu:user", this.openUserContextMenu);
|
eventbus.off("contextmenu:user", this.openUserContextMenu);
|
||||||
eventbus.off("contextmenu:channel", this.openChannelContextMenu);
|
eventbus.off("contextmenu:channel", this.openChannelContextMenu);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
enablePointerEvents() {
|
||||||
|
this.passthrough = false;
|
||||||
|
document.body.removeEventListener("pointerup", this.enablePointerEvents, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
openChannelContextMenu(data) {
|
openChannelContextMenu(data) {
|
||||||
|
if (data.event.type === "contextmenu") {
|
||||||
|
// Pass through all pointer events to allow the network list's
|
||||||
|
// dragging events to continue triggering.
|
||||||
|
this.passthrough = true;
|
||||||
|
document.body.addEventListener("pointerup", this.enablePointerEvents, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const items = generateChannelContextMenu(this.$root, data.channel, data.network);
|
const items = generateChannelContextMenu(this.$root, data.channel, data.network);
|
||||||
this.open(data.event, items);
|
this.open(data.event, items);
|
||||||
},
|
},
|
||||||
|
@ -82,6 +82,7 @@
|
|||||||
role="region"
|
role="region"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
@touchstart="onDraggableTouchStart"
|
@touchstart="onDraggableTouchStart"
|
||||||
|
@touchmove="onDraggableTouchMove"
|
||||||
@touchend="onDraggableTouchEnd"
|
@touchend="onDraggableTouchEnd"
|
||||||
@touchcancel="onDraggableTouchEnd"
|
@touchcancel="onDraggableTouchEnd"
|
||||||
>
|
>
|
||||||
@ -205,6 +206,8 @@ import JoinChannel from "./JoinChannel.vue";
|
|||||||
import socket from "../js/socket";
|
import socket from "../js/socket";
|
||||||
import collapseNetwork from "../js/helpers/collapseNetwork";
|
import collapseNetwork from "../js/helpers/collapseNetwork";
|
||||||
import isIgnoredKeybind from "../js/helpers/isIgnoredKeybind";
|
import isIgnoredKeybind from "../js/helpers/isIgnoredKeybind";
|
||||||
|
import distance from "../js/helpers/distance";
|
||||||
|
import eventbus from "../js/eventbus";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NetworkList",
|
name: "NetworkList",
|
||||||
@ -325,16 +328,25 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDraggableChoose(event) {
|
onDraggableChoose(event) {
|
||||||
if (this.isTouchEvent(event.originalEvent)) {
|
const original = event.originalEvent;
|
||||||
|
|
||||||
|
if (this.isTouchEvent(original)) {
|
||||||
// onDrag is only triggered when the user actually moves the
|
// onDrag is only triggered when the user actually moves the
|
||||||
// dragged object but onChoose is triggered as soon as the
|
// dragged object but onChoose is triggered as soon as the
|
||||||
// item is eligible for dragging. This gives us an opportunity
|
// item is eligible for dragging. This gives us an opportunity
|
||||||
// to tell the user they've held the touch long enough.
|
// to tell the user they've held the touch long enough.
|
||||||
event.item.classList.add("ui-sortable-dragging-touch-cue");
|
event.item.classList.add("ui-sortable-dragging-touch-cue");
|
||||||
|
|
||||||
|
if (original instanceof TouchEvent && original.touches.length > 0) {
|
||||||
|
this.startDrag = [original.touches[0].clientX, original.touches[0].clientY];
|
||||||
|
} else if (original instanceof PointerEvent) {
|
||||||
|
this.startDrag = [original.clientX, original.clientY];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDraggableUnchoose(event) {
|
onDraggableUnchoose(event) {
|
||||||
event.item.classList.remove("ui-sortable-dragging-touch-cue");
|
event.item.classList.remove("ui-sortable-dragging-touch-cue");
|
||||||
|
this.startDrag = null;
|
||||||
},
|
},
|
||||||
onDraggableTouchStart() {
|
onDraggableTouchStart() {
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
@ -343,6 +355,18 @@ export default {
|
|||||||
document.body.classList.add("force-no-select");
|
document.body.classList.add("force-no-select");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onDraggableTouchMove(event) {
|
||||||
|
if (this.startDrag && event.touches.length > 0) {
|
||||||
|
const touch = event.touches[0];
|
||||||
|
const currentPosition = [touch.clientX, touch.clientY];
|
||||||
|
|
||||||
|
if (distance(this.startDrag, currentPosition) > 10) {
|
||||||
|
// Context menu is shown on Android after long touch.
|
||||||
|
// Dismiss it now that we're sure the user is dragging.
|
||||||
|
eventbus.emit("contextmenu:cancel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onDraggableTouchEnd(event) {
|
onDraggableTouchEnd(event) {
|
||||||
if (event.touches.length === 0) {
|
if (event.touches.length === 0) {
|
||||||
document.body.classList.remove("force-no-select");
|
document.body.classList.remove("force-no-select");
|
||||||
|
@ -2252,6 +2252,14 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#context-menu-container.passthrough {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-menu-container.passthrough > * {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mentions-popup,
|
.mentions-popup,
|
||||||
#context-menu,
|
#context-menu,
|
||||||
.textcomplete-menu {
|
.textcomplete-menu {
|
||||||
|
5
client/js/helpers/distance.js
Normal file
5
client/js/helpers/distance.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
function distance([x1, y1], [x2, y2]) {
|
||||||
|
return Math.hypot(x1 - x2, y1 - y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default distance;
|
@ -1,5 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import distance from "./distance";
|
||||||
|
|
||||||
// onTwoFingerSwipe will be called with a cardinal direction ("n", "e", "s" or
|
// onTwoFingerSwipe will be called with a cardinal direction ("n", "e", "s" or
|
||||||
// "w") as its only argument.
|
// "w") as its only argument.
|
||||||
function listenForTwoFingerSwipes(onTwoFingerSwipe) {
|
function listenForTwoFingerSwipes(onTwoFingerSwipe) {
|
||||||
@ -89,10 +91,6 @@ function getSwipe(hist) {
|
|||||||
return getCardinalDirection(hist[0].center, hist[hist.length - 1].center);
|
return getCardinalDirection(hist[0].center, hist[hist.length - 1].center);
|
||||||
}
|
}
|
||||||
|
|
||||||
function distance([x1, y1], [x2, y2]) {
|
|
||||||
return Math.hypot(x1 - x2, y1 - y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCardinalDirection([x1, y1], [x2, y2]) {
|
function getCardinalDirection([x1, y1], [x2, y2]) {
|
||||||
// If θ is the angle of the vector then this is tan(θ)
|
// If θ is the angle of the vector then this is tan(θ)
|
||||||
const tangent = (y2 - y1) / (x2 - x1);
|
const tangent = (y2 - y1) / (x2 - x1);
|
||||||
|
Loading…
Reference in New Issue
Block a user