Commit 3d48b82e authored by Piotr Gawron's avatar Piotr Gawron
Browse files

touch interface is using default google maps implementation and right click should work fine

parent 4e153c47
Pipeline #4434 passed with stage
in 1 minute and 33 seconds
......@@ -42,33 +42,41 @@
"ProtVista": "git://github.com/davidhoksza/protvista.git#4e4bb737ba1e183291505bd25f8bae2e651ce21e",
"downloadjs": "1.4.7",
"jquery": "3.3.1",
"litemol": "github:dsehnal/LiteMol#a5419c696faa84530dd93acd55b747cf8136902b"
"litemol": "github:dsehnal/LiteMol#67556b0de0d2428f9494136758cbf8a662f66412"
},
"dependencies": {
"ProtVista": {
"version": "git://github.com/davidhoksza/protvista.git#4e4bb737ba1e183291505bd25f8bae2e651ce21e",
"dev": true,
"requires": {
"d3": "3.5.17",
"file-saver": "1.3.3",
"jquery": "2.2.4",
"jszip": "3.1.4",
"underscore": "1.8.3"
},
"dependencies": {
"jquery": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
"integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=",
"dev": true
}
}
},
"jquery": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==",
"dev": true
}
}
},
"ProtVista": {
"version": "git://github.com/davidhoksza/protvista.git#4e4bb737ba1e183291505bd25f8bae2e651ce21e",
"dev": true,
"requires": {
"d3": "3.5.17",
"file-saver": "1.3.3",
"jquery": "2.2.4",
"jszip": "3.1.4",
"underscore": "1.8.3"
},
"dependencies": {
"jquery": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
"integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=",
"dev": true
},
"litemol": {
"version": "github:dsehnal/LiteMol#67556b0de0d2428f9494136758cbf8a662f66412",
"dev": true,
"requires": {
"@types/react": "15.6.15",
"@types/react-dom": "15.5.7"
}
}
}
},
......@@ -2050,14 +2058,6 @@
"immediate": "3.0.6"
}
},
"litemol": {
"version": "github:dsehnal/LiteMol#a5419c696faa84530dd93acd55b747cf8136902b",
"dev": true,
"requires": {
"@types/react": "15.6.15",
"@types/react-dom": "15.5.7"
}
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
......
......@@ -66,11 +66,20 @@ GuiConnector.prototype.init = function () {
self.getParams[decode(arguments[1])] = decode(arguments[2]);
});
self._touchStartEvent = function (e) {
self.updateMouseCoordinates(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
};
self._touchMoveEvent = function (e) {
self.updateMouseCoordinates(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
};
// force browser to update mouse coordinates whenever mouse move
jQuery(document).ready(function () {
$(document).mousemove(function (e) {
self.updateMouseCoordinates(e.pageX, e.pageY);
});
$(document).on('touchstart', self._touchStartEvent);
$(document).on('touchmove', self._touchMoveEvent);
});
if (self._windowResizeEvents === undefined) {
......@@ -285,6 +294,11 @@ GuiConnector.prototype.destroy = function () {
if (self._errorDialog !== undefined) {
$(self._errorDialog).dialog("destroy").remove();
}
self._windowResizeEvents = undefined;
$(document).off('touchstart', self._touchStartEvent);
$(document).off('touchmove', self._touchMoveEvent);
};
GuiConnector.singleton = new GuiConnector();
......
......@@ -21,14 +21,24 @@ function ContextMenu(params) {
self._handledTimeStamp = undefined;
$('body').click(function (e) {
self.hide(e.timeStamp);
});
self.MIN_SHOW_TIME = 2000;
self._documentClickListener = function (e) {
if (e.target.className.indexOf("dropdown-link") === -1) {
self.hide(e.timeStamp - self.MIN_SHOW_TIME);
}
};
$(document).on('click', self._documentClickListener);
}
ContextMenu.prototype = Object.create(AbstractGuiElement.prototype);
ContextMenu.prototype.constructor = ContextMenu;
ContextMenu.prototype.destroy = function () {
var self = this;
$(document).off('click', self._documentClickListener);
};
ContextMenu.prototype.addOption = function (name, handler, disabled) {
if (!disabled) disabled = false;
var self = this;
......@@ -39,11 +49,12 @@ ContextMenu.prototype.addOption = function (name, handler, disabled) {
type: "li"
});
var link = Functions.createElement({
type: "a",
type: "a",
className: "dropdown-link",
content: name
});
if (!disabled) {
link.href = "#";
link.href = "#";
} else {
link.className = 'disabled-link';
}
......@@ -56,23 +67,27 @@ ContextMenu.prototype.addOption = function (name, handler, disabled) {
ContextMenu.prototype.open = function (x, y, timestamp) {
var self = this;
self._handledTimeStamp = timestamp;
$(self.getElement()).show().css({
position: "absolute",
left: x,
top: y
}).off('click').on('click', 'a', function (e) {
self.hide(e.timeStamp);
if ($(this).data("handler") !== undefined) {
return $(this).data("handler")();
} else {
logger.debug("Nothing to do");
//close if this is not a submenu
if (this.parentNode === undefined || this.parentNode.className.indexOf("dropdown-submenu") === -1) {
self.hide(e.timeStamp);
if ($(this).data("handler") !== undefined) {
return $(this).data("handler")();
} else {
logger.debug("Nothing to do");
}
}
});
};
ContextMenu.prototype.hide = function (timestamp) {
var self = this;
if (self._handledTimeStamp !== timestamp) {
if (self._handledTimeStamp < timestamp) {
self._handledTimeStamp = timestamp;
$(this.getElement()).hide();
}
......
......@@ -40,10 +40,10 @@ MapContextMenu.prototype.init = function() {
MapContextMenu.prototype.setMolStar = function(molStar){
this._molStar = molStar;
}
};
MapContextMenu.prototype.getMolStar = function(){
return this._molStar;
}
};
module.exports = MapContextMenu;
......@@ -28,6 +28,7 @@ SubMenu.prototype._createGui = function (params) {
var link = Functions.createElement({
type: "a",
href: "#",
className: "dropdown-link",
content: params.name
});
link.tabIndex = -1;
......@@ -52,6 +53,7 @@ SubMenu.prototype.addOption = function (name, handler) {
var link = Functions.createElement({
type: "a",
href: "#",
className: "dropdown-link",
content: name
});
$(link).data("handler", handler);
......
......@@ -379,9 +379,9 @@ AbstractCustomMap.prototype.registerMapClickEvents = function () {
// select last clicked map
google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function (mouseEvent) {
customMap.setActiveSubmapId(self.getId());
var coordinates = self.fromLatLngToPoint(mouseEvent.latLng)
var coordinates = self.fromLatLngToPoint(mouseEvent.latLng);
customMap.setActiveSubmapClickCoordinates(coordinates);
activateMolStarLink(coordinates, self);
return activateMolStarLink(coordinates, self);
});
// prepare for image export
......@@ -412,14 +412,35 @@ AbstractCustomMap.prototype.registerMapClickEvents = function () {
});
// context menu event
google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () {
self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime());
self.getElement().oncontextmenu = function (event) {
self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, event.timeStamp);
};
//long click should open context menu https://stackoverflow.com/a/38457006/1127920
var mousedUp = false;
var touchStartTimestamp;
$(self.getElement()).on("touchstart", function (event) {
mousedUp = false;
touchStartTimestamp = event.timeStamp;
setTimeout(function () {
if (mousedUp === false && touchStartTimestamp === event.timeStamp) {
self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, event.timeStamp);
}
}, 500);
});
google.maps.event.addListener(this.getGoogleMap(), 'mouseup', function () {
mousedUp = true;
});
google.maps.event.addListener(this.getGoogleMap(), 'dragstart', function () {
mousedUp = true;
});
};
function activateMolStarLink(coordinates, map) {
ServerConnector.getClosestElementsByCoordinates({
return ServerConnector.getClosestElementsByCoordinates({
modelId: map.getId(),
coordinates: coordinates,
count: 1
......@@ -446,8 +467,8 @@ function activateMolStarLink(coordinates, map) {
}
if (uniprotIds.length > 0) map.getContextMenu().getMolStar().activateInContextMenu(uniprotIds);
else map.getContextMenu().getMolStar().deactivateInContextMenu();
}, function () {
map.getContextMenu().getMolStar().deactivateInContextMenu();
}).catch(function () {
return map.getContextMenu().getMolStar().deactivateInContextMenu();
});
}
......@@ -1253,4 +1274,8 @@ AbstractCustomMap.prototype.getProject = function () {
return this._project;
};
AbstractCustomMap.prototype.destroy = function () {
$(this.getElement()).off("touchstart");
};
module.exports = AbstractCustomMap;
......@@ -18,7 +18,6 @@ var Reaction = require('./data/Reaction');
var ReferenceGenome = require('./data/ReferenceGenome');
var SecurityError = require('../SecurityError');
var Submap = require('./Submap');
var TouchMap = require('./TouchMap');
/**
* Default constructor.
......@@ -55,10 +54,6 @@ function CustomMap(options) {
// list of reference genomes
this._referenceGenome = [];
if (options.isCustomTouchInterface()) {
this._touchInterface = new TouchMap(this);
}
this.createSubmaps();
this._dialogs = [];
......@@ -1163,6 +1158,7 @@ CustomMap.prototype.getSubmaps = function () {
CustomMap.prototype.destroy = function () {
var self = this;
AbstractCustomMap.prototype.destroy.call(self);
var commentDialog = self.getCommentDialog();
if (commentDialog !== undefined) {
commentDialog.destroy();
......@@ -1171,6 +1167,12 @@ CustomMap.prototype.destroy = function () {
for (var i = 0; i < submaps.length; i++) {
submaps[i].destroy();
}
if (self.getContextMenu() !== undefined) {
self.getContextMenu().destroy();
}
if (self.getSelectionContextMenu() !== undefined) {
self.getSelectionContextMenu().destroy();
}
};
CustomMap.prototype.getVisibleDataOverlays = function () {
......
......@@ -5,7 +5,6 @@
var logger = require('../logger');
var AbstractCustomMap = require('./AbstractCustomMap');
var CustomMapOptions = require('./CustomMapOptions');
var TouchMap = require('./TouchMap');
/**
* Constructor of a submap. Submaps are created on application start. But dialog
......@@ -83,10 +82,6 @@ Submap.prototype.open = function (htmlTag) {
google.maps.event.trigger(self.getGoogleMap(), 'resize');
if (self.isCustomTouchInterface()) {
self._touchInterface = new TouchMap(self);
}
self.setupLayouts();
self.registerMapClickEvents();
......@@ -134,6 +129,7 @@ Submap.prototype.getProject = function () {
Submap.prototype.destroy = function () {
var self = this;
AbstractCustomMap.prototype.destroy.call(self);
if (self.initialized) {
$(self.htmlTag).dialog("destroy");
}
......
"use strict";
var logger = require('../logger');
var GuiConnector = require('../GuiConnector');
/**
* This class is responsible for touch interface on the map.
*/
function TouchMap(paramCustomMap) {
this._customMap = paramCustomMap;
this.setMap(paramCustomMap.getGoogleMap());
logger.info("Turning on custom touch interfaces");
var self = this;
var el = this.getMap().getDiv();
el.addEventListener('touchstart', function (evt) {
self.handleStart(evt);
}, true);
el.addEventListener("touchend", function (evt) {
self.handleEnd(evt);
}, true);
el.addEventListener("touchcancel", function (evt) {
self.handleCancel(evt);
}, true);
el.addEventListener("touchleave", function (evt) {
self.handleEnd(evt);
}, true);
el.addEventListener("touchmove", function (evt) {
self.handleMove(evt);
}, true);
this.clearTouchData();
this.latLng = new google.maps.LatLng(0.0, 0.0);
google.maps.event.addListener(this.getMap(), 'mouseover', function (mouseEvent) {
self.latLng = mouseEvent.latLng;
});
google.maps.event.addListener(this.getMap(), 'mousemove', function (mouseEvent) {
self.latLng = mouseEvent.latLng;
});
google.maps.event.addListener(this.getMap(), 'zoom_changed', function () {
self.getCustomMap().getTopMap().refreshMarkers();
});
}
TouchMap.prototype.getCustomMap = function () {
return this._customMap;
};
TouchMap.prototype.clearTouchData = function () {
this.firstFingerId = null;
this.firstStartX = null;
this.firstStartY = null;
this.firstEndX = null;
this.firstEndY = null;
this.secondFingerId = null;
this.secondStartX = null;
this.secondStartY = null;
this.secondEndX = null;
this.secondEndY = null;
this.startCoord = null;
this.startZoom = null;
this.ongoingTouches = [];
this.lastMoveDx = 0;
this.lastMoveDy = 0;
this.lastStartedFinger = null;
this.lastStartedTime = 0;
// for how small move the touch is recognized as click
this.clickRange = 10;
// minimum time for long click (in ms)
this.longClickTime = 1000;
};
TouchMap.prototype.handleStart = function (evt) {
logger.debug("touchstart.");
evt.preventDefault();
evt.stopPropagation();
var self = this;
var touches = evt.changedTouches;
for (var index = 0; index < touches.length; index++) {
var idx = self.ongoingTouchIndexById(touches[index].identifier);
if (idx >= 0) {
self.clearTouchData();
}
}
for (var i = 0; i < touches.length; i++) {
self.ongoingTouches.push(self.copyTouch(touches[i]));
logger.debug("touchstart: " + i + ". " + touches[i].identifier);
if (self.ongoingTouches.length === 1) {
self.firstFingerId = touches[i].identifier;
self.firstStartX = touches[i].clientX;
self.firstStartY = touches[i].clientY;
self.firstEndX = touches[i].clientX;
self.firstEndY = touches[i].clientY;
self.startCoord = self.getMap().getCenter();
self.lastMoveDx = 0;
self.lastMoveDy = 0;
GuiConnector.updateMouseCoordinates(touches[i].clientX, touches[i].clientY);
}
if (self.ongoingTouches.length === 2) {
self.secondFingerId = touches[i].identifier;
self.secondStartX = touches[i].clientX;
self.secondStartY = touches[i].clientY;
self.secondEndX = touches[i].clientX;
self.secondEndY = touches[i].clientY;
self.startZoom = self.getMap().getZoom();
self.lastZoom = self.startZoom;
for (var j = 0; j < self.ongoingTouches.length; j++) {
if (self.ongoingTouches[j].identifier === self.firstFingerId) {
self.firstStartX = self.ongoingTouches[j].clientX;
self.firstStartY = self.ongoingTouches[j].clientY;
self.startCoord = self.getMap().getCenter();
self.lastMoveDx = 0;
self.lastMoveDy = 0;
}
}
}
self.lastStartedFinger = touches[i].identifier;
self.lastStartedTime = (new Date()).getTime();
}
};
TouchMap.prototype.updateCoordinates = function (touch) {
var self = this;
if (touch.identifier === self.firstFingerId) {
self.firstEndX = touch.clientX;
self.firstEndY = touch.clientY;
GuiConnector.updateMouseCoordinates(touch.clientX, touch.clientY);
} else if (touch.identifier === self.secondFingerId) {
self.secondEndX = touch.clientX;
self.secondEndY = touch.clientY;
}
};
TouchMap.prototype.lineDistance = function (x1, y1, x2, y2) {
var xs = 0;
var ys = 0;
xs = x2 - x1;
xs = xs * xs;
ys = y2 - y1;
ys = ys * ys;
return Math.sqrt(xs + ys);
};
TouchMap.prototype.moveMap = function (dx, dy) {
var self = this;
self.getMap().panBy(dx - self.lastMoveDx, dy - self.lastMoveDy);
self.lastMoveDx = dx;
self.lastMoveDy = dy;
};
TouchMap.prototype.zoomMap = function (pointX, pointY, zoomLevel) {
var self = this;
if (self.lastZoom !== zoomLevel) {
var div = this.getMap().getDiv();
self.lastZoom = zoomLevel;
var topPos = $(div).offset().top;
var leftPos = $(div).offset().left;
pointX -= leftPos;
pointY -= topPos;
var height = $(div).height();
var width = $(div).width();
var dx = width / 2 - pointX;
var dy = height / 2 - pointY;
self.getMap().panBy(-dx, -dy);
self.getMap().setZoom(zoomLevel);
self.getMap().panBy(dx, dy);
}
};
TouchMap.prototype.makeMove = function () {
var self = this;
if (self.firstFingerId !== null && self.firstFingerId !== undefined) {
if (self.secondFingerId !== null && self.secondFingerId !== undefined) {
var dist1 = self.lineDistance(self.secondStartX, self.secondStartY, self.firstStartX, self.firstStartY);
var dist2 = self.lineDistance(self.secondEndX, self.secondEndY, self.firstEndX, self.firstEndY);
var zoomFactor = dist2 / dist1;
var changeLevels = Math.round((Math.log(zoomFactor) / Math.log(2)));
self.zoomMap(self.firstEndX, self.firstEndY, changeLevels + self.startZoom);
} else {
var dx = -self.firstEndX + self.firstStartX;
var dy = -self.firstEndY + self.firstStartY;
var dist = Math.abs(dx) + Math.abs(dy);
if (dist > self.clickRange) {
self.moveMap(dx, dy);
}
}
}
};
TouchMap.prototype.makeLeftClick = function (x, y) {
logger.debug("Make left click on " + x + ", " + y + ".");
var self = this;
var el = $(document.elementFromPoint(x, y));
// if we clicked on one of the elements on the map then emulate the click
if (el.attr('src') !== undefined || el.attr('id') !== undefined || el.attr('title') !== undefined) {
el.click();
} else {
var mev = {
stop: null,
latLng: self.getCustomMap().getMouseLatLng()
};
google.maps.event.trigger(self.getMap(), 'click', mev);
}
};
TouchMap.prototype.makeRightClick = function (x, y) {
logger.debug("Make right click on " + x + ", " + y);
var self = this;
var el = $(document.elementFromPoint(x, y));
// if we clicked on one of the elements on the map then emulate the click
if (el.attr('src') !== undefined || el.attr('id') !== undefined || el.attr('title') !== undefined) {
el.click();
} else {
var mev = {
stop: null,
latLng: self.getCustomMap().getMouseLatLng()
};
google.maps.event.trigger(self.getMap(), 'rightclick', mev);
}