Merge pull request #4332 from itsjohncs/android-context-menu

Enable Android's context menus in network list.
This commit is contained in:
Max Leiter 2021-11-02 11:57:24 -07:00 committed by GitHub
commit a8d438261a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 18 deletions

View file

@ -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,
});
}
}, },
}, },
}; };

View file

@ -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);
}, },

View file

@ -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");

View file

@ -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 {

View file

@ -0,0 +1,5 @@
function distance([x1, y1], [x2, y2]) {
return Math.hypot(x1 - x2, y1 - y2);
}
export default distance;

View file

@ -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);

View file

@ -127,6 +127,6 @@
} }
}, },
"resolutions": { "resolutions": {
"sortablejs": "1.14.0" "sortablejs": "git+https://github.com/itsjohncs/Sortable.git"
} }
} }

View file

@ -7527,10 +7527,9 @@ socks@^2.6.1:
ip "^1.1.5" ip "^1.1.5"
smart-buffer "^4.1.0" smart-buffer "^4.1.0"
sortablejs@1.10.2, sortablejs@1.14.0: sortablejs@1.10.2, "sortablejs@git+https://github.com/itsjohncs/Sortable.git":
version "1.14.0" version "1.14.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8" resolved "git+https://github.com/itsjohncs/Sortable.git#21053e18ea6501e2aac8cac9029872115ab82844"
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
source-list-map@^2.0.0, source-list-map@^2.0.1: source-list-map@^2.0.0, source-list-map@^2.0.1:
version "2.0.1" version "2.0.1"