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