diff --git a/index.php b/index.php
index 828d4106e..b901b98b5 100644
--- a/index.php
+++ b/index.php
@@ -1845,6 +1845,8 @@ function attr($attr, $file) {
+
+
@@ -1854,7 +1856,6 @@ function attr($attr, $file) {
-
diff --git a/resources/build/build.xml b/resources/build/build.xml
index 0d9cd1be7..a38f6e099 100644
--- a/resources/build/build.xml
+++ b/resources/build/build.xml
@@ -42,7 +42,7 @@
concatenating JavaScript...
-
+
concatenating CSS...
diff --git a/resources/js/UI/EventLayerAccordion.js b/resources/js/UI/EventLayerAccordion.js
index f9237f563..54e0bfca5 100644
--- a/resources/js/UI/EventLayerAccordion.js
+++ b/resources/js/UI/EventLayerAccordion.js
@@ -192,7 +192,7 @@ var EventLayerAccordion = Layer.extend(
_createK12VisibilityBtn: function(index, id, name, markersVisible, labelsVisible, startOpened, apiSource) {
var visibilityBtn, labelsBtn, availableBtn/*, removeBtn*/, markersHidden, labelsHidden, availableHidden, eventsDiv, self=this;
- let treeid = 'tree-'+id;
+ let treeid = 'tree_'+name;
var visState = Helioviewer.userSettings.get("state.eventLayerAvailableVisible");
if ( typeof visState == 'undefined') {
diff --git a/resources/js/UI/ImagePresets.js b/resources/js/UI/ImagePresets.js
index e79a177c8..606fd8261 100644
--- a/resources/js/UI/ImagePresets.js
+++ b/resources/js/UI/ImagePresets.js
@@ -227,6 +227,10 @@ var UserLayersPresets = Class.extend({
layers = layers.slice(1, -1);
settings['imageLayers'] = layers.split("],[");
+ if (typeof helioviewer.viewport._tileLayerManager == "undefined") {
+ return;
+ }
+
helioviewer.viewport._tileLayerManager.each(function(){
$(document).trigger("remove-tile-layer", [this.id]);
$("#" + this.id + " *[oldtitle]").qtip("destroy");
diff --git a/resources/js/UI/ZoomControls.js b/resources/js/UI/ZoomControls.js
new file mode 100644
index 000000000..4d4c63e15
--- /dev/null
+++ b/resources/js/UI/ZoomControls.js
@@ -0,0 +1,77 @@
+/**
+ * @fileOverview Contains the class definition for an ZoomControls class.
+ * @author Jeff Stys
+ * @author Keith Hughitt
+ * @author Daniel Garcia Briseno
+ */
+/*jslint browser: true, white: true, onevar: true, undef: true, nomen: false,
+eqeqeq: true, plusplus: true, bitwise: true, regexp: false, strict: true,
+newcap: true, immed: true, maxlen: 80, sub: true */
+/*globals $, Class */
+"use strict";
+
+class ZoomControls {
+ /**
+ * @constructs
+ *
+ * Creates a new ZoomControl
+ * @param {number} maxImageScale Maximum image scale.
+ * @param {number} nLevels Number of zoom levels
+ * @param {() => void} slideStart
+ * @param {(percentage: number) => void} setZoomLevel
+ */
+ constructor(maxImageScale, nLevels, slideStart, setZoomLevel) {
+ this._maxImageScale = maxImageScale;
+ this.nLevels = nLevels
+ this.slideStart = slideStart;
+ this.setZoomLevel = setZoomLevel;
+
+ this.zoomSlider = $('#zoomControlSlider');
+ $(document).bind("helioviewer-ready", this._initSlider.bind(this));
+ $(document).bind("update-viewport", this._updateSliderValue.bind(this));
+ }
+
+ get _currentLevel() {
+ return Math.log(helioviewer.getZoomedImageScale() / this._maxImageScale) / Math.log(0.5);
+ }
+
+ /**
+ * Update the slider value
+ */
+ _updateSliderValue() {
+ let currentValue = this.zoomSlider.slider("value");
+ // Only change the value if it is significantly different.
+ // Otherwise the slider value is changed, which triggers a zoom update
+ // which triggers a slider update, and we have an infinite loop.
+ let dt = Math.abs(currentValue - this._currentLevel);
+ if (dt > 0.01) {
+ this.zoomSlider.slider("value", this._currentLevel);
+ }
+ }
+
+ /**
+ * @description Initializes zoom level slider
+ */
+ _initSlider() {
+ // Initialize slider
+ this.zoomSlider.slider({
+ start: this.slideStart,
+ slide: this.onslide.bind(this),
+ min: 0,
+ max: this.nLevels,
+ step: 1,
+ orientation: 'vertical',
+ value: this._currentLevel
+ });
+
+ // Add tooltip
+ let description = "Drag this handle up and down to zoom in and out of " +
+ "the displayed image.";
+ $("#zoomControlSlider > .ui-slider-handle").attr('title', description)
+ .qtip();
+ }
+
+ onslide(event, slider) {
+ this.setZoomLevel(slider.value);
+ }
+}
\ No newline at end of file
diff --git a/resources/js/Viewport/HelioviewerViewport.js b/resources/js/Viewport/HelioviewerViewport.js
index defb14b6d..a81aea530 100644
--- a/resources/js/Viewport/HelioviewerViewport.js
+++ b/resources/js/Viewport/HelioviewerViewport.js
@@ -231,7 +231,7 @@ var HelioviewerViewport = Class.extend(
$(this.domNode).bind("mousedown", $.proxy(this.onMouseMove, this));
this.domNode.dblclick($.proxy(this.doubleClick, this));
- $('#center-button').click($.proxy(this.centerViewportOnBiggestLayer, this));
+ $('#center-button').click(() => {$(document).trigger('center-viewport');});
$(window).resize($.proxy(this.resize, this));
},
@@ -399,8 +399,11 @@ var HelioviewerViewport = Class.extend(
* @param {Event} e Event class
*/
doubleClick: function (event) {
- this.movementHelper.doubleClick(event);
-
+ let anchor = {
+ left: event.pageX,
+ top: event.pageY
+ };
+ this.helioZoom.setAnchorForCenter(anchor);
if (event.shiftKey) {
$("#zoom-out-button").click();
} else {
diff --git a/resources/js/Viewport/Helper/HelioviewerZoomer.js b/resources/js/Viewport/Helper/HelioviewerZoomer.js
index 67d56633b..5fe26b397 100644
--- a/resources/js/Viewport/Helper/HelioviewerZoomer.js
+++ b/resources/js/Viewport/Helper/HelioviewerZoomer.js
@@ -1,3 +1,6 @@
+const MIN_THRESHOLD = 0.5;
+const MAX_THRESHOLD = 1.5;
+
/**
* This module helps with handling smooth zoom scaling via
* CSS transforms.
@@ -14,16 +17,44 @@
this._initZoomLevel();
this._initializePinchListeners();
this._zoomInBtn = document.getElementById('zoom-in-button');
- this._zoomInBtn.addEventListener('click', this._smoothZoomIn.bind(this));
this._zoomOutBtn = document.getElementById('zoom-out-button');
- this._zoomOutBtn.addEventListener('click', this._smoothZoomOut.bind(this));
this._mc = document.getElementById('moving-container');
this._sandbox = document.getElementById('sandbox');
this._scale = 1;
this._anchor = {left: 0, top: 0};
this._last_size = 0;
this._css_rules = [];
+ this._maxImageScale = zoomLevels[0];
+ this._minImageScale = zoomLevels[zoomLevels.length - 1];
+ this._slider = new ZoomControls(this._maxImageScale, zoomLevels.length - 1, this._targetCenter.bind(this), this.jumpToZoomLevel.bind(this));
Helioviewer.userSettings.set('mobileZoomScale', 1);
+
+ // Make sure the sun is centered when the user requests centering the viewport
+ $(document).bind("center-viewport", this._resetOrigin.bind(this));
+
+ // Register zoom in button click and zoom-in event
+ this._zoomInBtn.addEventListener('click', this._smoothZoomIn.bind(this));
+ $(document).bind("zoom-in", this._smoothZoomIn.bind(this));
+
+ // Register zoom out button click and zoom-out event.
+ this._zoomOutBtn.addEventListener('click', this._smoothZoomOut.bind(this));
+ $(document).bind("zoom-out", this._smoothZoomOut.bind(this));
+ }
+
+ /**
+ * Sets the anchor to the center of the viewport
+ */
+ _targetCenter() {
+ let center = {left: window.innerWidth / 2, top: window.innerHeight / 2};
+ this.setAnchorForCenter(center);
+ }
+
+ /**
+ * Resets the transform origin property from the moving container
+ * so that the transform origin is at sun center.
+ */
+ _resetOrigin() {
+ this.setAnchor({left: 0, top: 0});
}
/**
@@ -82,7 +113,6 @@
let new_top = apparent_y + (targetScale - 1) * new_anchor_y;
// Sandbox may shift, account for this by tracking its position.
- // TODO: I'll have to deal with this later.
let initial_sandbox_position = $(this._sandbox).position();
let closure = () => {
@@ -111,10 +141,7 @@
*/
_zoomIn() {
let nextValue = this._zoomIndex + 1;
- if (nextValue < this.zoomLevels.length) {
- this._zoomIndex = nextValue;
- this._setAppImageScale(this._zoomIndex);
- }
+ this._setZoomLevel(nextValue);
}
/**
@@ -123,8 +150,18 @@
*/
_zoomOut() {
let nextValue = this._zoomIndex - 1;
- if (nextValue >= 0) {
- this._zoomIndex = nextValue;
+ this._setZoomLevel(nextValue);
+ }
+
+ /**
+ * Forces the application zoom resolution to the given level
+ * @param {number} level index into zoomLevels. Lower is lower res, higher is higher res.
+ */
+ _setZoomLevel(level) {
+ // Enforce that the given value is an integer
+ level = Math.ceil(level);
+ if (0 <= level && level < this.zoomLevels.length) {
+ this._zoomIndex = level;
this._setAppImageScale(this._zoomIndex);
}
}
@@ -157,9 +194,9 @@
setScale(scale) {
// Limit scale to 2.5 and 0.25
if (0.25 <= scale && scale <= 2.5) {
- if (scale >= 1.5) {
+ if (scale >= MAX_THRESHOLD) {
this._zoomHelioviewer(scale, true);
- } else if (scale <= 0.5) {
+ } else if (scale <= MIN_THRESHOLD) {
this._zoomHelioviewer(scale, false);
} else {
Helioviewer.userSettings.set('mobileZoomScale', scale);
@@ -265,6 +302,7 @@
* @param {number} duration Length of animation in seconds
*/
_animateZoom(factor, duration) {
+ clearInterval(this._animate_interval);
// Compute animation frame details.
let fps = 120;
let frame_delay = 1/fps;
@@ -276,10 +314,17 @@
let frame_delta = delta_scale / num_frames;
let ticks = 0;
- let interval = setInterval(() => {
+ this._animate_interval = setInterval(() => {
+ let lastScale = this._scale;
this.setScale(this._scale + frame_delta);
+ if ((factor > 1) && (this._scale < lastScale)) {
+ frame_delta /= 2;
+ }
+ if ((factor < 1) && (this._scale > lastScale)) {
+ frame_delta *= 2;
+ }
ticks += 1;
- if (ticks == num_frames) { clearInterval(interval); }
+ if (ticks == num_frames) { clearInterval(this._animate_interval); }
}, frame_delay)
}
@@ -287,13 +332,27 @@
* Executed when the zoom in button is clicked.
*/
_smoothZoomIn() {
- this._animateZoom(2, 0.25);
+ this._animateZoom(2, 0.2);
}
/**
* Executed when the zoom out button is clicked.
*/
_smoothZoomOut() {
- this._animateZoom(0.5, 0.25);
+ this._animateZoom(0.5, 0.2);
+ }
+
+ /**
+ * Sets the zoom to the given percentage without animation.
+ * @note This is used by the slider in mininal view for animating zoom.
+ * @param {number} level Continuous index into zoom levels (can be decimal)
+ */
+ jumpToZoomLevel(level) {
+ while (level > this._zoomIndex) {
+ this._zoomHelioviewer(2, true);
+ }
+ while (level < this._zoomIndex) {
+ this._zoomHelioviewer(0.5, false);
+ }
}
};
diff --git a/resources/js/Viewport/Helper/MouseCoordinates.js b/resources/js/Viewport/Helper/MouseCoordinates.js
index 8fd39183c..40173c1c1 100644
--- a/resources/js/Viewport/Helper/MouseCoordinates.js
+++ b/resources/js/Viewport/Helper/MouseCoordinates.js
@@ -91,10 +91,10 @@ var MouseCoordinates = Class.extend(
//scale
let zoom = (Helioviewer.userSettings.get('mobileZoomScale') || 1);
- scale = this.imageScale / zoom;
+ let scale = this.imageScale / zoom;
// TODO: Apply scaling fix depending on the current layer
- x = scale * mouse_pos.x;
- y = scale * mouse_pos.y;
+ let x = scale * mouse_pos.x;
+ let y = scale * mouse_pos.y;
let correctedCoord = this.correctCoordinate(scale, mouse_pos.x, -mouse_pos.y)
// Return scaled coords
diff --git a/resources/js/Viewport/Helper/ScrollZoom.js b/resources/js/Viewport/Helper/ScrollZoom.js
index 21c7a0a02..cc1fb3c7c 100644
--- a/resources/js/Viewport/Helper/ScrollZoom.js
+++ b/resources/js/Viewport/Helper/ScrollZoom.js
@@ -1,8 +1,8 @@
class ScrollZoom {
constructor() {
+ this._MIN_SPEED = 3;
+ this._MAX_DURATION_MS = 25;
this._scrolling = false;
- this._delta = 0;
- this._timeout = undefined;
document.getElementById('sandbox').addEventListener("wheel", this._wheeling.bind(this));
}
@@ -11,8 +11,6 @@ class ScrollZoom {
* @param {WheelEvent} event
*/
_wheeling(event) {
- this._ClearTimeout();
- this._SetTimeout();
if (!this._scrolling) {
this._StartScrolling(event);
} else {
@@ -29,30 +27,52 @@ class ScrollZoom {
let position = { left: event.pageX, top: event.pageY };
this._onstart(position);
}
+ this._currentZoom = 0;
+ this._target = 0;
+ this._delta = 0;
+ this._interval = setInterval(this._rectifyScroll.bind(this), 1);
}
/**
- * @param {WheelEvent} event
+ * Smooths the scroll amount for a consistent scroll experience using
+ * different mice which send different deltas
*/
- _UpdateScrolling(event) {
- this._delta -= event.deltaY;
- if (this._onupdate) {
- this._onupdate(this._delta);
+ _rectifyScroll() {
+ this._currentZoom += this._delta;
+ // Finish scrolling when the zoom crosses the target.
+ if (((this._delta > 0) && (this._currentZoom > this._target))
+ || ((this._delta < 0) && (this._currentZoom < this._target))) {
+ this._FinishScrolling();
+ } else if (this._onupdate) {
+ this._onupdate(this._currentZoom);
}
}
- _ClearTimeout() {
- if (this._timeout) {
- clearTimeout(this._timeout);
+ /**
+ * @param {WheelEvent} event
+ */
+ _UpdateScrolling(event) {
+ // Update the target with the amount of delta sent by the wheel input.
+ this._target -= event.deltaY;
+ // The rectify function is scheduled to run every 1ms.
+ // We would like the animation to complete in less than 50ms, so the zoom
+ // should change by 1/250 each 'frame'.
+ this._delta = (this._target - this._currentZoom) / this._MAX_DURATION_MS;
+
+ // Enforce a minimum scroll speed.
+ // Otherwise small scrolls will do a painfully slow animation.
+ if ((this._delta < 0) && (this._delta > -this._MIN_SPEED)) {
+ this._delta = -2;
+ }
+ if ((this._delta > 0) && (this._delta < this._MIN_SPEED)) {
+ this._delta = 2;
}
}
- _SetTimeout() {
- this._timeout = setTimeout(() => {
- this._scrolling = false;
- this._timeout = undefined;
- this._delta = 0;
- }, 250);
+ _FinishScrolling() {
+ this._scrolling = false;
+ this._delta = 0;
+ clearInterval(this._interval);
}
onstart(fn) {