Merge pull request #4326 from itsjohncs/mobile-channel-list-sorting

Allow network list reordering via touch.
This commit is contained in:
Max Leiter 2021-10-12 15:53:18 -07:00 committed by GitHub
commit fcffab1259
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 30 deletions

View file

@ -82,11 +82,15 @@ export default {
this.$root.switchToChannel(this.channel); this.$root.switchToChannel(this.channel);
}, },
openContextMenu(event) { openContextMenu(event) {
eventbus.emit("contextmenu:channel", { // events.buttons will be 0 when the event is caused by a long
event: event, // touch on Android.
channel: this.channel, if (event.buttons !== 0) {
network: this.network, eventbus.emit("contextmenu:channel", {
}); event: event,
channel: this.channel,
network: this.network,
});
}
}, },
}, },
}; };

View file

@ -56,17 +56,18 @@
<Draggable <Draggable
v-else v-else
:list="$store.state.networks" :list="$store.state.networks"
:filter="isCurrentlyInTouch" :delay="LONG_TOUCH_DURATION"
:prevent-on-filter="false" :delay-on-touch-only="true"
:touch-start-threshold="10"
handle=".channel-list-item[data-type='lobby']" handle=".channel-list-item[data-type='lobby']"
draggable=".network" draggable=".network"
ghost-class="ui-sortable-ghost" ghost-class="ui-sortable-ghost"
drag-class="ui-sortable-dragged" drag-class="ui-sortable-dragging"
group="networks" group="networks"
class="networks" class="networks"
@change="onNetworkSort" @change="onNetworkSort"
@start="onDragStart" @choose="onDraggableChoose"
@end="onDragEnd" @unchoose="onDraggableUnchoose"
> >
<div <div
v-for="network in $store.state.networks" v-for="network in $store.state.networks"
@ -80,6 +81,9 @@
class="network" class="network"
role="region" role="region"
aria-live="polite" aria-live="polite"
@touchstart="onDraggableTouchStart"
@touchend="onDraggableTouchEnd"
@touchcancel="onDraggableTouchEnd"
> >
<NetworkLobby <NetworkLobby
:network="network" :network="network"
@ -100,15 +104,16 @@
<Draggable <Draggable
draggable=".channel-list-item" draggable=".channel-list-item"
ghost-class="ui-sortable-ghost" ghost-class="ui-sortable-ghost"
drag-class="ui-sortable-dragged" drag-class="ui-sortable-dragging"
:group="network.uuid" :group="network.uuid"
:filter="isCurrentlyInTouch"
:prevent-on-filter="false"
:list="network.channels" :list="network.channels"
:delay="LONG_TOUCH_DURATION"
:delay-on-touch-only="true"
:touch-start-threshold="10"
class="channels" class="channels"
@change="onChannelSort" @change="onChannelSort"
@start="onDragStart" @choose="onDraggableChoose"
@end="onDragEnd" @unchoose="onDraggableUnchoose"
> >
<template v-for="(channel, index) in network.channels"> <template v-for="(channel, index) in network.channels">
<Channel <Channel
@ -247,6 +252,10 @@ export default {
this.setActiveSearchItem(); this.setActiveSearchItem();
}, },
}, },
created() {
// Number of milliseconds a touch has to last to be considered long
this.LONG_TOUCH_DURATION = 500;
},
mounted() { mounted() {
Mousetrap.bind("alt+shift+right", this.expandNetwork); Mousetrap.bind("alt+shift+right", this.expandNetwork);
Mousetrap.bind("alt+shift+left", this.collapseNetwork); Mousetrap.bind("alt+shift+left", this.collapseNetwork);
@ -280,16 +289,6 @@ export default {
return false; return false;
}, },
isCurrentlyInTouch(e) {
// TODO: Implement a way to sort on touch devices
return e.pointerType !== "mouse";
},
onDragStart(e) {
e.target.classList.add("ui-sortable-active");
},
onDragEnd(e) {
e.target.classList.remove("ui-sortable-active");
},
onNetworkSort(e) { onNetworkSort(e) {
if (!e.moved) { if (!e.moved) {
return; return;
@ -317,6 +316,38 @@ export default {
order: channel.network.channels.map((c) => c.id), order: channel.network.channels.map((c) => c.id),
}); });
}, },
isTouchEvent(event) {
// This is the same way Sortable.js detects a touch event. See
// SortableJS/Sortable@daaefeda:/src/Sortable.js#L465
return (
(event.touches && event.touches[0]) ||
(event.pointerType && event.pointerType === "touch")
);
},
onDraggableChoose(event) {
if (this.isTouchEvent(event.originalEvent)) {
// onDrag is only triggered when the user actually moves the
// dragged object but onChoose is triggered as soon as the
// item is eligible for dragging. This gives us an opportunity
// to tell the user they've held the touch long enough.
event.item.classList.add("ui-sortable-dragging-touch-cue");
}
},
onDraggableUnchoose(event) {
event.item.classList.remove("ui-sortable-dragging-touch-cue");
},
onDraggableTouchStart() {
if (event.touches.length === 1) {
// This prevents an iOS long touch default behavior: selecting
// the nearest selectable text.
document.body.classList.add("force-no-select");
}
},
onDraggableTouchEnd(event) {
if (event.touches.length === 0) {
document.body.classList.remove("force-no-select");
}
},
toggleSearch(event) { toggleSearch(event) {
if (isIgnoredKeybind(event)) { if (isIgnoredKeybind(event)) {
return true; return true;

View file

@ -107,6 +107,10 @@ body {
overflow: hidden; /* iOS Safari requires overflow rather than overflow-y */ overflow: hidden; /* iOS Safari requires overflow rather than overflow-y */
} }
body.force-no-select * {
user-select: none !important;
}
a, a,
a:hover, a:hover,
a:focus { a:focus {
@ -707,14 +711,19 @@ background on hover (unless active) */
/* Remove background on hovered/active channel when sorting/drag-and-dropping */ /* Remove background on hovered/active channel when sorting/drag-and-dropping */
.ui-sortable-ghost, .ui-sortable-ghost,
.channel-list-item.ui-sortable-dragged, .ui-sortable-dragging .channel-list-item,
.ui-sortable-dragged .channel-list-item, .ui-sortable-dragging,
.ui-sortable-active .channel-list-item:hover, .ui-sortable-dragging:hover,
.ui-sortable-active .channel-list-item.active { .ui-sortable-dragging.active,
.ui-sortable-dragging-touch-cue .channel-list-item,
.ui-sortable-dragging-touch-cue,
.ui-sortable-dragging-touch-cue:hover,
.ui-sortable-dragging-touch-cue.active {
background: transparent; background: transparent;
} }
.ui-sortable-ghost::after { .ui-sortable-ghost::after,
.ui-sortable-dragging-touch-cue:not(.ui-sortable-dragging)::after {
background: var(--body-bg-color); background: var(--body-bg-color);
border: 1px dashed #99a2b4; border: 1px dashed #99a2b4;
border-radius: 6px; border-radius: 6px;
@ -727,6 +736,10 @@ background on hover (unless active) */
right: 10px; right: 10px;
} }
.ui-sortable-dragging-touch-cue:not(.ui-sortable-ghost)::after {
background: transparent;
}
#sidebar .network { #sidebar .network {
position: relative; position: relative;
margin-bottom: 20px; margin-bottom: 20px;