'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2018 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['baglass/core-client/js/core-client/ui/core/View', 'jquery', 'underscore', 'gemini/app/util/ScreenReaderUtil', 'storytelling/nls/StringResources', 'text!./templates/TimeIndicator.html'], function (View, $, _, ScreenReaderUtil, stringResources, Template) { var TimelineTimeIndicatorView = View.extend({ templateString: Template, _allowDrag: true, _isDragging: false, _dragInfo: null, _seekRefreshInterval: 200, _seekRefreshTimer: -1, _scrollEventHandler: null, lastScroll: 0, init: function init(options) { TimelineTimeIndicatorView.inherited('init', this, arguments); this.controller = options.controller; this.controller.on('time:update', this.onTimeUpdated, this); this.scaleManager = options.scaleManager; this.scaleManager.on('scale:change', this.onScaleChanged, this); this._ScreenReader = new ScreenReaderUtil(); this._callOut = _.debounce(this._updateAriaLabel.bind(this), options.callOutDelay || 600); this._indicatorOnly = options.indicatorOnly === true; if (this._indicatorOnly && typeof options.progressBarCallback === 'function') { this.progressBarCallback = options.progressBarCallback; } }, remove: function remove() { if (this.$handle) { this.$handle.off('focus touchstart touchend mousedown mouseup keydown'); this.$handle.hammer().off('dragstart dragleft dragright dragend'); this.$handle = null; } if (this.controller) { this.controller.off('time:update', this.onTimeUpdated, this); } if (this.scaleManager) { this.scaleManager.off('scale:change', this.onScaleChanged, this); } TimelineTimeIndicatorView.inherited('remove', this, arguments); }, /** * Renders the film strip * * @returns */ render: function render() { var sHtml = this.dotTemplate({ allInfo: !this._indicatorOnly, handleLabel: stringResources.get('timelinePositionIndicator', { position: this.getCurrentTime() }) }); this.$el.html(sHtml); this.$handle = this.$el.find('div.handle'); if (!this._indicatorOnly) { this.$handleValue = this.$handle.find('div:nth-of-type(1)'); this.$handleArrow = this.$handle.find('div:nth-of-type(2)'); } this.$handle.on('focus', this.handleTimelinePositionIndicatorMove.bind(this)).on('touchstart', this.onTouchStart.bind(this)).on('touchend', this.onTouchEnd.bind(this)).on('mousedown', this.onTouchStart.bind(this)).on('mouseup', this.onTouchEnd.bind(this)).on('keydown', this._onKeyDown.bind(this)); this.$handle.hammer().on('dragstart', this.onDragStart.bind(this)).on('dragleft', this.onDragLeftRight.bind(this)).on('dragright', this.onDragLeftRight.bind(this)).on('dragend', this.onDragEnd.bind(this)); // Set the initial position. this._moveIndicatorWithTime(this.controller.getCurrentTime()); return this; }, setFocus: function setFocus() { this.$handle.focus(); }, getCurrentTime: function getCurrentTime() { return this.controller.getTimeLabel(this.controller.getCurrentTime(), true); }, getEndTime: function getEndTime() { return this.controller.getTimeLabel(this.controller.getDuration(), true); }, onScroll: function onScroll(event) { this.lastScroll = event.scrollLeft; this.refresh(); }, refresh: function refresh() { this._moveIndicatorWithTime(this.controller.getCursorTime()); }, /* * View events */ onTouchStart: function onTouchStart() { this.$el.addClass('dragging'); this.wasPlaying = this.controller.isPlaying(); // Pause when interacting with the indicator. if (this.wasPlaying) { this.controller.pause(); } }, onTouchEnd: function onTouchEnd() { this.$el.removeClass('dragging'); // Start playing if we were originally playing before the drag. if (this.wasPlaying) { this.wasPlaying = false; this.controller.play(); } }, onDragStart: function onDragStart(event) { event.gesture.preventDefault(); this.$el.addClass('dragging'); this._setDragStartInfo(); }, onDragLeftRight: function onDragLeftRight(event) { event.gesture.preventDefault(); var position = this._getBoundedPosition(event.gesture.deltaX); this._moveIndicatorWithPosition(position); }, onDragEnd: function onDragEnd(event) { event.gesture.preventDefault(); this._clearInterval(); this.$el.removeClass('dragging'); this._isDragging = false; var position = this._getBoundedPosition(event.gesture.deltaX); this._moveIndicatorWithPosition(position); this._updateTimeFromDrag(); this._dragInfo = null; }, _onKeyDown: function _onKeyDown(evt) { if (this._shouldScrub(evt)) { // Arrow key with no shift key this._onArrowKeyPress(evt); } else if (this._shouldJumpToNextMarker(evt)) { // Shift + (Left or Up) when parent view is the timeline or navigate markers is enabled this.controller.jumpToNextMarker(); this.handlePositionMoveRight(); evt.stopPropagation(); } else if (this._shouldJumpToPreviousMarker(evt)) { // Shift + (Right or Down) when parent view is the timeline or navigate markers is enabled this.controller.jumpToPreviousMarker(); this.handlePositionMoveLeft(); evt.stopPropagation(); } else if (this._shouldJumpToStartOfScene(evt)) { // Shift + (Left or Up) when parent view is the progress bar this.controller.setCurrentTime(0); this._updateAriaLabel('sceneStart'); evt.stopPropagation(); } else if (this._shouldJumpToEndOfScene(evt)) { // Shift + (Right or Down) when parent view is the progress bar this.controller.setCurrentTime(this.controller.getDuration()); this._updateAriaLabel('sceneEnd'); evt.stopPropagation(); } else if (this._shouldPerformTogglePlayPause(evt)) { // Spacebar when parent view is the progress bar or the timeline this.controller.eventRouter.trigger('playback:togglePlayPause'); evt.stopPropagation(); } }, // Left or Right with no shift key _shouldScrub: function _shouldScrub(evt) { return !evt.shiftKey && [37, 38, 39, 40].indexOf(evt.keyCode) !== -1; }, // Shift + (Left or Up) when parent view is the timeline _shouldJumpToPreviousMarker: function _shouldJumpToPreviousMarker(evt) { var isTimeline = this.$el.hasClass('timelinePosition'); return (isTimeline || this.controller.isNavigateMarkers()) && evt.shiftKey && [37, 38].indexOf(evt.keyCode) !== -1; }, // Shift + (Right or Down) when parent view is the timeline _shouldJumpToNextMarker: function _shouldJumpToNextMarker(evt) { var isTimeline = this.$el.hasClass('timelinePosition'); return (isTimeline || this.controller.isNavigateMarkers()) && evt.shiftKey && [39, 40].indexOf(evt.keyCode) !== -1; }, // Shift + (Left or Up) when parent view is the progress bar _shouldJumpToStartOfScene: function _shouldJumpToStartOfScene(evt) { var isProgressBar = this.$el.hasClass('progressBarPosition'); return isProgressBar && evt.shiftKey && [37, 38].indexOf(evt.keyCode) !== -1; }, // Shift + (Right or Down) when parent view is the progress bar _shouldJumpToEndOfScene: function _shouldJumpToEndOfScene(evt) { var isProgressBar = this.$el.hasClass('progressBarPosition'); return isProgressBar && evt.shiftKey && [39, 40].indexOf(evt.keyCode) !== -1; }, // Space with parent view timeline or progress bar _shouldPerformTogglePlayPause: function _shouldPerformTogglePlayPause(evt) { return evt.keyCode === 32; }, _onArrowKeyPress: function _onArrowKeyPress(evt) { var $target = $(evt.currentTarget); // Set as selected $target.focus(); // The minimum we can move is 1 tick. // we are free to scale it with shift/ctrl later. var moveByDelta = this.controller.getTickDuration(); switch (evt.keyCode) { // left key case 37: case 38: moveByDelta *= -1; break; // right key case 39: case 40: break; default: return; } evt.stopPropagation(); evt.preventDefault(); this.controller.setCurrentTime(this.controller.getCurrentTime() + moveByDelta); if (moveByDelta < 0) { this._callOut('timelinePositionIndicatorMoveLeftTo'); } else { this._callOut('timelinePositionIndicatorMoveRightTo'); } this._dragInfo = null; }, /* * Controller events. */ onScaleChanged: function onScaleChanged() { this._moveIndicatorWithTime(this.controller.getCurrentTime()); }, onTimeUpdated: function onTimeUpdated(event) { if (!this._isDragging) { this._moveIndicatorWithTime(event.currentTime); } }, /* * Helpers */ // Public Helpers handleTimelinePositionIndicatorMove: function handleTimelinePositionIndicatorMove() { this._callOut('timelinePositionIndicator'); }, handlePositionMoveLeft: function handlePositionMoveLeft() { var time = this.getCurrentTime(); var translationKey = time === '0:00.0' ? 'sceneStart' : 'timelinePositionIndicatorMoveLeftTo'; this._callOut(translationKey); }, handlePositionMoveRight: function handlePositionMoveRight() { var translationKey = this.controller.isAtEndOfScene() ? 'sceneEnd' : 'timelinePositionIndicatorMoveRightTo'; this._callOut(translationKey); }, // Private Helpers _setDragStartInfo: function _setDragStartInfo() { this._isDragging = true; this._dragInfo = {}; this._dragInfo.initialScroll = this.lastScroll; this._dragInfo.left = this.$el.position().left + this.lastScroll; this._dragInfo.currentLeft = this._dragInfo.left; this._clearInterval(); this._seekRefreshTimer = setInterval(this._updateTimeFromDrag.bind(this), this._seekRefreshInterval); }, _updateTimeFromDrag: function _updateTimeFromDrag() { // Calculate the time based on the position of the indicator var time = this.scaleManager.convertPositionToTime(this._dragInfo.currentLeft); this.controller.setCurrentTime(time); }, _moveIndicatorWithTime: function _moveIndicatorWithTime(endTime) { var position = this.scaleManager.convertTimeToPosition(endTime); this._moveIndicatorWithPosition(position); }, _moveIndicatorWithPosition: function _moveIndicatorWithPosition(position) { if (!this.$handle) { return; } if (!this._indicatorOnly) { //round to the nearest tick. This is the minimum amount we can deal with. var time = this.scaleManager.convertPositionToTime(position); var tick = this.controller.getTickDuration(); time = Math.round(time / tick) * tick; position = this.scaleManager.convertTimeToPosition(time); } var leftVal = void 0; var percent = void 0; if (this._indicatorOnly) { var maxPos = this._getMaxPosition(); if (maxPos === 0) { percent = 0; } else { percent = position / maxPos * 100; } leftVal = 'calc(' + percent + '% - ' + this.lastScroll + 'px)'; } else { leftVal = position - this.lastScroll + 'px'; } this.$el.css({ 'left': leftVal }); this._moveHelper(position); if (this._indicatorOnly && this.progressBarCallback) { this.progressBarCallback(percent); } }, _moveHelper: function _moveHelper(position) { var halfWidth = this.$handle.outerWidth(false) / 2; var margin = position - halfWidth < 0 ? position : halfWidth; var marginLeft = Math.max(0, margin - 7); var borderLeftWidth = Math.max(0, Math.min(7, margin)); // Get the cursor time. var time = this.scaleManager.convertPositionToTime(position); // _moveHelper is expensive (the two css() calls below) and gets called A LOT, so if nothing is changed, return immediately if (margin === this._prevMargin && marginLeft === this._preMarginLeft && borderLeftWidth === this._prevBorderLeftWidth && time === this._prevTime) { return; } this._preMarginLeft = marginLeft; this._prevMargin = margin; this._prevBorderLeftWidth = borderLeftWidth; this._prevTime = time; if (!this._indicatorOnly) { this.$handle.css('margin-left', -margin + 'px'); this.$handleArrow.css({ 'margin-left': marginLeft + 'px', 'border-left-width': borderLeftWidth + 'px' }); // Update the cursor label. this._updateTimeLabel(time); } if (this._dragInfo) { this._dragInfo.currentLeft = position; } // Update the controller. this.controller.setCursorTime(time); }, _getMaxPosition: function _getMaxPosition() { return this.scaleManager.convertTimeToPosition(this.controller.getDuration()); }, _getCursorPosition: function _getCursorPosition(delta) { return this._dragInfo.left + delta + this.lastScroll - this._dragInfo.initialScroll; }, _getBoundedPosition: function _getBoundedPosition(delta) { return Math.max(0, Math.min(this._getCursorPosition(delta), this._getMaxPosition())); }, _updateTimeLabel: function _updateTimeLabel(time) { this.$handleValue.text(this.controller.getTimeLabel(time, 1), true); }, _updateAriaLabel: function _updateAriaLabel(msg) { if (this.$handle) { var sMessage = stringResources.get(msg, { position: this.getCurrentTime() }); this.$handle.attr({ 'aria-label': sMessage }); this._ScreenReader.callOut(sMessage); } }, _clearInterval: function _clearInterval() { if (this._seekRefreshTimer >= 0) { clearInterval(this._seekRefreshTimer); this._seekRefreshTimer = -1; } } }); return TimelineTimeIndicatorView; }); //# sourceMappingURL=TimelineTimeIndicatorView.js.map