diff --git a/web/assets/css/transmission-app.scss b/web/assets/css/transmission-app.scss index 57cfc0fab..64ba4cf0c 100644 --- a/web/assets/css/transmission-app.scss +++ b/web/assets/css/transmission-app.scss @@ -64,6 +64,7 @@ $image-play-circle-idle: '../img/play-circle-idle.svg'; display: inline-block; padding: 3px; user-select: none; + -webkit-user-select: none; } /// GLOBAL @@ -279,6 +280,7 @@ $toolbar-height: $toolbar-height-number * 1px; height: $toolbar-height; margin-right: 6px; user-select: none; + -webkit-user-select: none; width: $toolbar-height; svg { @@ -455,6 +457,8 @@ $video-image: '../img/film.svg'; padding: 0; text-align: left; width: 100%; + user-select: none; + -webkit-user-select: none; .torrent:nth-child(even) { background-color: var(--color-bg-even); @@ -466,7 +470,6 @@ $video-image: '../img/film.svg'; .torrent { border-bottom: 1px solid var(--color-default-border); - user-select: none; .icon { // color the background svg fill @@ -894,6 +897,7 @@ $video-image: '../img/film.svg'; #prefs-dialog.ui-tabs .ui-tabs-panel { padding: 0; user-select: none; + -webkit-user-select: none; } .prefs-section { @@ -1331,6 +1335,8 @@ $video-image: '../img/film.svg'; padding: 10px 5px; position: absolute; z-index: 9999; + user-select: none; + -webkit-user-select: none; .context-menuitem { font-size: 13px; diff --git a/web/src/context-menu.js b/web/src/context-menu.js index 9685d7557..546c673c8 100644 --- a/web/src/context-menu.js +++ b/web/src/context-menu.js @@ -47,6 +47,10 @@ export class ContextMenu extends EventTarget { const root = document.createElement('div'); root.role = 'menu'; root.classList.add('context-menu', 'popup'); + root.addEventListener('contextmenu', (e_) => { + e_.preventDefault(); + }); + root.style.pointerEvents = 'none'; const actions = {}; const add_item = (action, warn = false) => { diff --git a/web/src/transmission.js b/web/src/transmission.js index 4b7ccbe06..37a78db98 100644 --- a/web/src/transmission.js +++ b/web/src/transmission.js @@ -51,6 +51,9 @@ export class Transmission extends EventTarget { this.boundPopupCloseListener = this.popupCloseListener.bind(this); + this.isTouch = 'ontouchstart' in window ? true : false; + this.busyclick = false; + // listen to actions // TODO: consider adding a mutator listener here to see dynamic additions for (const element of document.querySelectorAll(`button[data-action]`)) { @@ -211,9 +214,13 @@ export class Transmission extends EventTarget { torrent_list: document.querySelector('#torrent-list'), }; - this.elements.torrent_list.addEventListener('contextmenu', (event_) => { - // ensure the clicked row is selected - let row_element = event.target; + const rightc = (event_) => { + if (this.isTouch && event_.touches.length > 1) { + return; + } + + // if not already, highlight the torrent + let row_element = event_.target; while (row_element && !row_element.classList.contains('torrent')) { row_element = row_element.parentNode; } @@ -222,23 +229,58 @@ export class Transmission extends EventTarget { this._setSelectedRow(row); } + // open context menu const popup = new ContextMenu(this.action_manager); this.setCurrentPopup(popup); const boundingElement = document.querySelector('#torrent-container'); const bounds = boundingElement.getBoundingClientRect(); const x = Math.min( - event_.x, + this.isTouch ? event_.touches[0].clientX : event_.x, bounds.x + bounds.width - popup.root.clientWidth, ); const y = Math.min( - event_.y, + this.isTouch ? event_.touches[0].clientY : event_.y, bounds.y + bounds.height - popup.root.clientHeight, ); popup.root.style.left = `${x > 0 ? x : 0}px`; popup.root.style.top = `${y > 0 ? y : 0}px`; event_.preventDefault(); - }); + }; + + if (this.isTouch) { + this.elements.torrent_list.addEventListener('touchstart', (event_) => { + if (this.busyclick) { + clearTimeout(this.busyclick); + this.busyclick = false; + } else { + this.busyclick = setTimeout(rightc.bind(this), 500, event_); + } + }); + this.elements.torrent_list.addEventListener('touchend', () => { + clearTimeout(this.busyclick); + this.busyclick = false; + setTimeout(() => { + if (this.popup) { + this.popup.root.style.pointerEvents = 'auto'; + } + }, 1); + }); + this.elements.torrent_list.addEventListener('touchmove', () => { + clearTimeout(this.busyclick); + this.busyclick = false; + }); + this.elements.torrent_list.addEventListener('contextmenu', (event_) => { + event_.preventDefault(); + }); + } else { + this.elements.torrent_list.addEventListener('contextmenu', (event_) => { + rightc(event_); + if (this.popup) { + this.popup.root.style.pointerEvents = 'auto'; + } + }); + } // Get preferences & torrents from the daemon this.loadDaemonPrefs(); @@ -754,7 +796,6 @@ TODO: fix this when notifications get fixed // which deselects all on click event_.stopPropagation(); - // TODO: long-click should raise inspector if (event_.shiftKey) { this._selectRange(row); // Need to deselect any selected text