Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,103 @@
define([
'require',
'exports',
'module',
'debug',
'lib/panzoom',
'lib/orientation',
'MediaFrame',
'lib/bind',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:confirm');
var addPanAndZoomHandlers = require('lib/panzoom');
var orientation = require('lib/orientation');
var MediaFrame = require('MediaFrame');
var bind = require('lib/bind');
var View = require('view');
module.exports = View.extend({
name: 'confirm',
initialize: function () {
this.on('destroy', this.onDestroy);
},
render: function () {
this.el.innerHTML = this.template();
this.els.mediaFrame = this.find('.js-media-frame');
this.els.retake = this.find('.js-retake');
this.els.select = this.find('.js-select');
this.disableButtons();
this.setupMediaFrame();
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.retake, 'click', this.onButtonClick);
bind(this.els.select, 'click', this.onButtonClick);
return this;
},
setupMediaFrame: function () {
this.mediaFrame = new MediaFrame(this.els.mediaFrame, true, this.maxPreviewSize);
this.mediaFrame.video.onloading = this.onVideoLoading;
this.mediaFrame.video.onplaying = this.onVideoPlaying;
addPanAndZoomHandlers(this.mediaFrame);
window.addEventListener('resize', this.onResize);
return this;
},
onVideoLoading: function () {
this.emit('loadingvideo', 'loadingVideo');
},
onVideoPlaying: function () {
this.emit('playingvideo');
},
clearMediaFrame: function () {
this.mediaFrame.clear();
this.disableButtons();
},
hide: function () {
this.el.classList.add('hidden');
orientation.lock();
},
show: function () {
this.el.classList.remove('hidden');
orientation.unlock();
},
disableButtons: function () {
this.els.retake.setAttribute('disabled', true);
this.els.select.setAttribute('disabled', true);
},
enableButtons: function () {
this.els.retake.removeAttribute('disabled');
this.els.select.removeAttribute('disabled');
},
showImage: function (image) {
this.enableButtons();
this.mediaFrame.displayImage(image.blob, image.width, image.height, image.preview, image.rotation, image.mirrored);
return this;
},
showVideo: function (video) {
this.enableButtons();
this.mediaFrame.displayVideo(video.blob, video.poster.blob, video.poster.width / video.poster.height, video.rotation);
return this;
},
template: function () {
return '<div class="confirm-media-frame js-media-frame"></div>' + '<footer id="confirm-controls" class="confirm-controls">' + '<button class="retake-button js-retake" data-l10n-id="retake-button" ' + 'name="retake"></button>' + '<button class="select-button test-confirm-select js-select" ' + 'data-l10n-id="select-button" name="select"></button></footer>';
},
onButtonClick: function (event) {
var el = event.currentTarget;
var name = el.getAttribute('name');
this.emit('click:' + name);
},
onResize: function () {
this.mediaFrame.resize();
if (this.mediaFrame.displayingVideo) {
this.mediaFrame.video.setPlayerSize();
}
},
onDestroy: function () {
window.removeEventListener('resize', this.onResize);
this.mediaFrame.clear();
}
});
});

View file

@ -0,0 +1,52 @@
define([
'require',
'exports',
'module',
'debug',
'view'
], function (require, exports, module) {
var debug = require('debug')('view:countdown');
var View = require('view');
module.exports = View.extend({
name: 'countdown',
initialize: function () {
this.render();
},
render: function () {
this.el.innerHTML = this.template();
this.els.count = this.find('.js-count');
delete this.template;
debug('rendered');
return this;
},
set: function (time) {
this.els.count.textContent = time;
this.el.classList.remove('shrink');
this.reflow = this.el.offsetTop;
this.el.classList.add('shrink');
debug('set time: %s, near: %s', time);
return this;
},
show: function () {
this.el.classList.remove('hidden');
this.el.classList.add('visible');
},
hide: function (callback) {
this.el.classList.remove('visible');
this.el.classList.add('hidden');
setTimeout(callback, 200);
return this;
},
setImmanent: function (value) {
this.el.classList.toggle('immanent', value);
},
reset: function () {
this.els.count.textContent = '';
this.el.classList.remove('immanent', 'shrink');
return this;
},
template: function () {
return '<div class="countdown_circle-1"></div>' + '<div class="countdown_circle-2"></div>' + '<div class="countdown_count">' + '<div class="rotates js-count"></div>' + '</div>';
}
});
});

View file

@ -0,0 +1,27 @@
define([
'require',
'exports',
'module',
'debug',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:indicators');
var View = require('view');
module.exports = View.extend({
name: 'indicators',
tag: 'ul',
initialize: function () {
this.render();
},
render: function () {
this.el.innerHTML = this.template();
delete this.template;
debug('rendered');
return this;
},
template: function () {
return '<li class="indicator_countdown rotates" data-icon="self-timer" ' + 'data-l10n-id="self-timer-indicator"></li>' + '<li class="indicator_hdr rotates" data-icon="hdr" ' + 'data-l10n-id="hdr-indicator"></li>' + '<li class="indicator_geolocation rotates" data-icon="location" ' + 'data-l10n-id="location-indicator"></li>' + '<li class="indicator_battery rotates" data-icon="battery-3" ' + 'data-l10n-id="battery-low-indicator"></li>';
}
});
});

View file

@ -0,0 +1,84 @@
define([
'require',
'exports',
'module',
'debug',
'view',
'lib/bind'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:overlay');
var View = require('view');
var bind = require('lib/bind');
module.exports = View.extend({
className: 'overlay',
initialize: function (options) {
var data = this.getData(options.type);
if (!data) {
return;
}
this.data('type', options.type);
this.data('closable', options.closable);
this.render(data);
},
render: function (data) {
this.el.innerHTML = this.template(data);
this.els.buttons = { close: this.find('.js-close-btn') };
delete this.template;
debug('rendered');
return this.bindEvents();
},
rendered: function () {
return this.el.innerHTML.length > 0;
},
bindEvents: function () {
bind(this.els.buttons.close, 'click', this.onButtonClick);
return this;
},
template: function (data) {
return '<form role="dialog" data-type="confirm">' + '<section>' + '<h1 class="overlay-title" data-l10n-id="' + data.title + '"></h1>' + '<p id="overlay-text" data-l10n-id="' + data.body + '"></p>' + '</section>' + '<menu class="overlay-menu-close">' + '<button class="full js-close-btn" type="button" ' + 'data-l10n-id="' + data.closeButtonText + '" name="close-btn">' + '</button>' + '</menu>' + '</form>';
},
getData: function (type) {
var data = {};
switch (type) {
case 'unavailable':
data.title = 'nocard2-title';
data.body = 'nocard3-text';
break;
case 'nospace':
data.title = 'nospace2-title';
data.body = 'nospace2-text';
break;
case 'shared':
data.title = 'pluggedin2-title';
data.body = 'pluggedin2-text';
break;
case 'shutdown':
data.title = 'battery-shutdown-title';
data.body = 'battery-shutdown-text';
break;
case 'request-fail':
data.title = 'camera-unavailable-title';
data.body = 'camera-unavailable-text';
break;
default:
return false;
}
data.closeButtonText = 'close-button';
return data;
},
data: function (key, value) {
switch (arguments.length) {
case 1:
return this.el.getAttribute('data-' + key);
case 2:
this.el.setAttribute('data-' + key, value);
}
},
onButtonClick: function (event) {
var el = event.currentTarget;
var name = el.getAttribute('name');
this.emit('click:' + name);
}
});
});

View file

@ -0,0 +1,230 @@
define([
'require',
'debug',
'lib/panzoom',
'lib/orientation',
'MediaFrame',
'lib/bind',
'attach',
'view',
'gaia-header'
], function (require) {
'use strict';
var debug = require('debug')('view:preview-gallery');
var addPanAndZoomHandlers = require('lib/panzoom');
var orientation = require('lib/orientation');
var MediaFrame = require('MediaFrame');
var bind = require('lib/bind');
var attach = require('attach');
var View = require('view');
require('gaia-header');
var SWIPE_DISTANCE_THRESHOLD = window.innerWidth / 3;
var SWIPE_VELOCITY_THRESHOLD = 1;
var SWIPE_DURATION = 150;
var FADE_IN_DURATION = 450;
return View.extend({
name: 'preview-gallery',
className: 'offscreen',
initialize: function () {
debug('initialized');
},
render: function () {
this.el.innerHTML = this.template();
this.els.frameContainer = this.find('.js-frame-container');
this.els.previewMenu = this.find('.js-preview-menu');
this.els.mediaFrame = this.find('.js-media-frame');
this.els.countText = this.find('.js-count-text');
this.els.options = this.find('.js-options');
this.els.header = this.find('.js-header');
this.els.share = this.find('.js-share');
window.dispatchEvent(new CustomEvent('lazyload', { detail: this.el }));
this.configure();
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.el, 'tap', this.onTap);
bind(this.els.header, 'action', this.firer('click:back'));
bind(this.els.options, 'click', this.onButtonClick);
bind(this.els.share, 'click', this.onButtonClick);
bind(this.els.mediaFrame, 'wheel', this.onFrameWheel);
return this;
},
configure: function () {
this.currentIndex = this.lastIndex = 0;
this.frame = new MediaFrame(this.els.mediaFrame, true, this.maxPreviewSize);
this.frame.video.onloading = this.onVideoLoading;
this.frame.video.onplaying = this.onVideoPlaying;
this.frame.video.onpaused = this.onVideoPaused;
addPanAndZoomHandlers(this.frame, this.swipeCallback);
},
template: function () {
return '<div class="preview-menu js-preview-menu">' + '<gaia-header class="js-header" action="back" ignore-dir>' + '<h1 data-l10n-id="preview">Preview</h1>' + '<button class="preview-share-icon js-share"' + 'name="share" data-icon="share" ' + 'data-l10n-id="share-button"></button>' + '<button class="preview-option-icon ' + 'js-options" name="options" data-icon="more" ' + 'data-l10n-id="more-button"></button>' + '</gaia-header>' + '</div>' + '<div class="frame-container js-frame-container">' + '<div class="media-frame js-media-frame"></div>' + '</div>' + '<div class="count-text js-count-text"></div>';
},
onTap: function () {
if (this.videoPlaying) {
return;
}
var isShown = this.els.previewMenu.classList.contains('visible');
if (isShown) {
this.previewMenuFadeOut();
} else {
this.previewMenuFadeIn();
}
},
previewMenuFadeIn: function () {
this.els.previewMenu.classList.add('visible');
},
previewMenuFadeOut: function () {
this.els.previewMenu.classList.remove('visible');
},
onFrameWheel: function (event) {
if (event.deltaMode !== event.DOM_DELTA_PAGE || event.deltaY) {
return;
}
if (this.videoPlaying) {
this.onVideoPaused();
}
if (event.deltaX > 0) {
this.emit('swipe', 'left');
} else if (event.deltaX < 0) {
this.emit('swipe', 'right');
}
},
swipeCallback: function (swipeAmount, swipeVelocity) {
var self = this;
if (swipeVelocity === undefined) {
this.els.frameContainer.style.transform = 'translate(' + swipeAmount + 'px, 0)';
return;
}
var direction, translation;
if (swipeAmount > 0 && swipeVelocity > 0 && this.currentIndex > 1 && (swipeAmount > SWIPE_DISTANCE_THRESHOLD || swipeVelocity > SWIPE_VELOCITY_THRESHOLD)) {
direction = 'right';
translation = '100%';
} else if (swipeAmount <= 0 && swipeVelocity <= 0 && this.currentIndex < this.lastIndex && (swipeAmount < -SWIPE_DISTANCE_THRESHOLD || swipeVelocity < -SWIPE_VELOCITY_THRESHOLD)) {
direction = 'left';
translation = '-100%';
}
if (!direction) {
animate('transform', 'translate(0,0)', SWIPE_DURATION);
return;
}
if (this.videoPlaying) {
this.onVideoPaused();
}
animate('transform', 'translate(' + translation + ', 0)', SWIPE_DURATION, function () {
self.emit('swipe', direction);
window.requestAnimationFrame(function () {
self.els.frameContainer.style.opacity = 0;
self.els.frameContainer.style.transform = 'translate(0,0)';
window.requestAnimationFrame(function () {
animate('opacity', 1, FADE_IN_DURATION);
});
});
});
function animate(property, value, duration, done) {
var e = self.els.frameContainer;
e.addEventListener('transitionend', onTransitionEnd);
e.style.transitionProperty = property;
e.style.transitionDuration = duration + 'ms';
e.style[property] = value;
function onTransitionEnd() {
e.removeEventListener('transitionend', onTransitionEnd);
delete e.style.transitionProperty;
delete e.style.transitionDuration;
if (done) {
done();
}
}
}
},
onButtonClick: function (e, el) {
if (this.videoPlaying) {
return;
}
el = el || e.currentTarget;
var name = el.getAttribute('name');
if (this.optionsMenuContainer) {
this.hideOptionsMenu();
}
this.emit('click:' + name, e);
e.stopPropagation();
},
open: function () {
window.addEventListener('resize', this.onResize);
orientation.unlock();
this.previewMenuFadeIn();
this.el.classList.remove('offscreen');
},
close: function () {
window.removeEventListener('resize', this.onResize);
orientation.lock();
this.previewMenuFadeOut();
this.el.classList.add('offscreen');
this.frame.clear();
},
updateCountText: function (current, total) {
this.currentIndex = current;
this.lastIndex = total;
this.els.countText.textContent = current + '/' + total;
this.els.mediaFrame.setAttribute('data-l10n-id', 'media-frame');
this.els.mediaFrame.setAttribute('data-l10n-args', JSON.stringify({
total: total,
current: current
}));
},
onResize: function () {
this.frame.resize();
if (this.frame.displayingVideo) {
this.frame.video.setPlayerSize();
}
},
showImage: function (image) {
this.frame.displayImage(image.blob, image.width, image.height, image.preview, image.rotation, image.mirrored);
},
showVideo: function (video) {
this.frame.displayVideo(video.blob, video.poster.blob, video.poster.width / video.poster.height, video.rotation);
},
onVideoLoading: function () {
this.emit('loadingvideo', 'loadingVideo');
},
onVideoPlaying: function () {
if (this.videoPlaying) {
return;
}
this.videoPlaying = true;
this.previewMenuFadeOut();
this.emit('playingvideo');
},
onVideoPaused: function () {
if (!this.videoPlaying) {
return;
}
this.videoPlaying = false;
this.previewMenuFadeIn();
},
showOptionsMenu: function () {
this.optionsMenuContainer = document.createElement('div');
this.optionsMenuContainer.innerHTML = this.optionTemplate();
this.el.appendChild(this.optionsMenuContainer);
this.el.classList.add('action-menu');
this.menu = this.find('.js-menu');
var cancelButton = this.find('.js-cancel');
bind(cancelButton, 'click', this.hideOptionsMenu);
if (this.menu) {
attach.on(this.menu, 'click', '.js-btn', this.onButtonClick);
}
},
hideOptionsMenu: function () {
if (this.optionsMenuContainer) {
this.el.classList.remove('action-menu');
this.optionsMenuContainer.parentElement.removeChild(this.optionsMenuContainer);
this.optionsMenuContainer = null;
}
},
optionTemplate: function () {
return '<form class="visible" data-type="action"' + 'role="dialog" data-z-index-level="action-menu">' + '<header data-l10n-id="options">Options</header>' + '<menu class="js-menu">' + '<button class="js-btn" name="gallery" data-l10n-id="open-gallery">' + 'Open Gallery' + '</button>' + '<button class="js-btn" name="delete" data-l10n-id="delete">' + 'Delete' + '</button>' + '<button class="js-cancel" data-action="cancel" data-l10n-id="cancel">' + 'Cancel' + '</button>' + '</menu>' + '</form>';
}
});
});

View file

@ -0,0 +1,24 @@
define([
'require',
'exports',
'module',
'debug',
'lib/format-timer',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:recording-timer');
var formatTimer = require('lib/format-timer');
var View = require('view');
module.exports = View.extend({
name: 'recording-timer',
initialize: function () {
this.value(0);
this.el.setAttribute('role', 'timer');
},
value: function (value) {
this.el.textContent = formatTimer(value);
debug('set value: %s', value);
}
});
});

View file

@ -0,0 +1,76 @@
define([
'require',
'exports',
'module',
'debug',
'attach',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:setting-options');
var attach = require('attach');
var View = require('view');
module.exports = View.extend({
name: 'setting-options',
initialize: function (options) {
this.model = options.model;
this.l10n = options.l10n || document.l10n;
this.on('destroy', this.onDestroy);
attach(this.el, 'click', 'li', this.onOptionClick);
attach(this.el, 'click', '.js-back', this.firer('click:back'));
this.model.on('change:selected', this.onSelectedChange);
debug('initialized');
},
onDestroy: function () {
this.model.off('change:selected', this.onSelectedChange);
},
onOptionClick: function (event, el) {
var key = el.getAttribute('data-key');
this.emit('click:option', key, this.model);
},
onSelectedChange: function (key) {
var next = this.els[key];
this.els.selected.classList.remove('selected');
next.classList.add('selected');
this.els.selected = next;
},
render: function () {
var data = this.model.get();
this.selectedKey = data.selected;
this.el.innerHTML = this.template(data);
this.els.ul = this.find('.js-list');
var localizable = data.optionsLocalizable === false ? false : true;
data.options.forEach(this.renderOption.bind(this, localizable));
delete this.template;
debug('rendered');
return this;
},
renderOption: function (localizable, option) {
var li = document.createElement('li');
var isSelected = option.key === this.selectedKey;
if (localizable) {
li.setAttribute('data-l10n-id', option.title);
this.l10n.formatValue(option.title).then(title => {
li.setAttribute('aria-label', title);
});
} else {
li.textContent = option.title;
li.setAttribute('aria-label', option.title);
}
li.setAttribute('data-key', option.key);
li.setAttribute('role', 'option');
li.className = 'setting-option';
li.dataset.icon = 'tick';
this.els.ul.appendChild(li);
this.els[option.key] = li;
if (isSelected) {
li.classList.add('selected');
li.setAttribute('aria-selected', true);
this.els.selected = li;
}
},
template: function (data) {
return '<div class="inner">' + '<div class="settings_header">' + '<div class="settings-back-btn js-back" ' + 'data-icon="left" role="button" data-l10n-id="back-button"></div>' + '<h2 aria-level="1" class="settings_title" data-l10n-id="' + data.header + '"></h2>' + '</div>' + '<div class="settings_items">' + '<ul role="listbox" class="inner js-list"></ul>' + '</div>' + '</div>';
}
});
});

View file

@ -0,0 +1,58 @@
define([
'require',
'exports',
'module',
'debug',
'lib/bind',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:setting');
var bind = require('lib/bind');
var View = require('view');
module.exports = View.extend({
tag: 'li',
name: 'setting',
initialize: function (options) {
this.l10n = options.l10n || document.l10n;
this.model = options.model;
this.model.on('change', this.render);
this.on('destroy', this.onDestroy);
this.el.dataset.icon = this.model.get('icon');
this.el.classList.add('test-' + this.model.get('title') + '-setting');
bind(this.el, 'click', this.onClick);
},
onClick: function () {
this.emit('click', this);
},
onDestroy: function () {
this.model.off('change', this.render);
},
render: function () {
var data = this.model.get();
data.selected = this.model.selected();
data.value = data.selected && data.selected.title;
this.el.setAttribute('role', 'option');
var localizedValue;
if (data.optionsLocalizable === false) {
localizedValue = Promise.resolve(data.value);
} else {
localizedValue = this.l10n.formatValue(data.value);
}
localizedValue.then(valueString => {
this.l10n.setAttributes(this.el, 'setting-option-' + data.title, { value: valueString });
this.el.innerHTML = this.template({
titleL10n: 'data-l10n-id="' + data.title + '"',
valueL10n: data.optionsLocalizable !== false ? 'data-l10n-id="' + data.value + '"' : '',
valueText: data.optionsLocalizable === false ? data.value : ''
});
delete this.template;
debug('rendered (item %s)', data.key);
});
return this;
},
template: function (data) {
return '<div class="setting_text">' + '<h4 class="setting_title" ' + data.titleL10n + '></h4>' + '<h5 class="setting_value" ' + data.valueL10n + '>' + data.valueText + '</h5>' + '</div>';
}
});
});

View file

@ -0,0 +1,97 @@
define([
'require',
'exports',
'module',
'debug',
'views/setting-options',
'views/setting',
'view',
'lib/bind'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:settings');
var OptionsView = require('views/setting-options');
var SettingView = require('views/setting');
var View = require('view');
var bind = require('lib/bind');
module.exports = View.extend({
name: 'settings',
fadeTime: 150,
initialize: function (options) {
this.OptionsView = options.OptionsView || OptionsView;
this.items = options.items;
this.children = [];
this.on('destroy', this.onDestroy);
bind(this.el, 'click', this.onClick);
},
onClick: function (e) {
e.stopPropagation();
},
onItemClick: function (view) {
this.showSetting(view.model);
},
showSetting: function (model) {
this.optionsView = new this.OptionsView({ model: model }).render().appendTo(this.els.pane2).on('click:option', this.firer('click:option')).on('click:back', this.goBack);
this.showPane(2);
},
onDestroy: function () {
this.children.forEach(this.destroyChild);
this.destroyOptionsView();
debug('destroyed');
},
render: function () {
this.el.innerHTML = this.template();
this.els.items = this.find('.js-items');
this.els.pane2 = this.find('.js-pane-2');
this.els.close = this.find('.js-close');
this.items.forEach(this.addItem);
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.close, 'click', this.firer('click:close'));
return this;
},
goBack: function () {
this.showPane(1);
this.destroyOptionsView();
},
destroyChild: function (view) {
view.destroy();
debug('destroyed child');
},
destroyOptionsView: function () {
if (this.optionsView) {
this.optionsView.destroy();
this.optionsView = null;
debug('options view destroyed');
}
},
addItem: function (model) {
var setting = new SettingView({ model: model }).render().appendTo(this.els.items).on('click', this.onItemClick);
this.children.push(setting);
debug('add item key: %s', model.key);
},
showPane: function (name) {
this.el.setAttribute('show-pane', 'pane-' + name);
},
fadeIn: function (done) {
setTimeout(this.show);
if (!done) {
return;
}
setTimeout(done, this.fadeTime);
},
fadeOut: function (done) {
this.hide();
if (!done) {
return;
}
setTimeout(done, this.fadeTime);
},
template: function () {
return '<div class="pane pane-1">' + '<div class="inner">' + '<div class="settings_inner">' + '<div class="settings_header">' + '<h2 class="settings_title" data-l10n-id="options" ' + 'aria-level="1"></h2>' + '</div>' + '<div class="settings_items">' + '<ul class="inner js-items" role="listbox"></ul>' + '</div>' + '</div>' + '</div>' + '</div>' + '<div class="pane pane-2">' + '<div class="inner js-pane-2"></div>' + '</div>' + '<div role="button" class="settings_close js-close" data-icon="menu" ' + 'data-l10n-id="close-menu-button"></div>';
}
});
});

View file

@ -0,0 +1,170 @@
define([
'require',
'exports',
'module',
'debug',
'view',
'lib/bind',
'lib/orientation'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:zoom-bar');
var View = require('view');
var bind = require('lib/bind');
var orientation = require('lib/orientation');
var lastTouch = null;
var clamp = function (value, minimum, maximum) {
return Math.min(Math.max(value, minimum), maximum);
};
module.exports = View.extend({
name: 'zoom-bar',
initialize: function () {
this.render();
this._orientation = orientation.get();
this.zoomBarIndicatorInterval = 10;
this.zoomBarInactivityTimeout = 3000;
},
render: function () {
this.el.innerHTML = this.template();
this.els.maxIndicator = this.find('.zoom-bar-max-indicator');
this.els.minIndicator = this.find('.zoom-bar-min-indicator');
this.els.inner = this.find('.zoom-bar-inner');
this.els.track = this.find('.zoom-bar-track');
this.els.scrubber = this.find('.zoom-bar-scrubber');
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.scrubber, 'touchstart', this.onTouchStart);
bind(this.els.scrubber, 'touchmove', this.onTouchMove);
bind(this.els.scrubber, 'touchend', this.onTouchEnd);
bind(this.els.maxIndicator, 'click', this.onIncrement);
bind(this.els.minIndicator, 'click', this.onDecrement);
orientation.on('orientation', this.setOrientation);
return this;
},
template: function () {
return '<div class="zoom-bar-max-indicator"></div>' + '<div class="zoom-bar-inner">' + '<div class="zoom-bar-track"></div>' + '<div class="zoom-bar-scrubber"></div>' + '</div>' + '<div class="zoom-bar-min-indicator"></div>';
},
onTouchStart: function (evt) {
lastTouch = evt.touches[0];
this.resetInactivityTimeout();
this.setScrubberActive(true);
this._innerHeight = this.els.inner.offsetHeight;
evt.stopPropagation();
},
onTouchMove: function (evt) {
if (!lastTouch) {
return;
}
var touch = evt.touches[0];
var deltaX = lastTouch.pageX - touch.pageX;
var deltaY = lastTouch.pageY - touch.pageY;
var scale = 100 / this._innerHeight;
deltaX *= scale;
deltaY *= scale;
switch (this._orientation) {
case 0:
this.setValue(this._value + deltaY, true);
break;
case 90:
this.setValue(this._value + deltaX, true);
break;
case 180:
this.setValue(this._value - deltaY, true);
break;
case 270:
this.setValue(this._value - deltaX, true);
break;
}
lastTouch = touch;
},
onTouchEnd: function (evt) {
if (!lastTouch) {
return;
}
lastTouch = null;
this.resetInactivityTimeout();
this.setScrubberActive(false);
},
onIncrement: function (evt) {
this.setValue(this._value + this.zoomBarIndicatorInterval, true);
this.flashScrubberActive();
evt.stopPropagation();
},
onDecrement: function (evt) {
this.setValue(this._value - this.zoomBarIndicatorInterval, true);
this.flashScrubberActive();
evt.stopPropagation();
},
_orientation: 0,
setOrientation: function (orientation) {
var el = this.el;
el.classList.remove('zooming');
window.requestAnimationFrame(function () {
el.style.transitionDuration = '0ms';
window.requestAnimationFrame(function () {
el.style.transitionDuration = '';
});
});
this._orientation = orientation;
},
_value: 0,
setValue: function (value, emitChange) {
this.resetInactivityTimeout();
var lastValue = this._value;
this._value = clamp(value, 0, 100);
if (this._value === lastValue) {
return;
}
if (this._value === 0) {
this.els.minIndicator.classList.add('active');
} else {
this.els.minIndicator.classList.remove('active');
}
if (this._value === 100) {
this.els.maxIndicator.classList.add('active');
} else {
this.els.maxIndicator.classList.remove('active');
}
var self = this;
window.requestAnimationFrame(function () {
self.els.track.style.top = 100 - self._value + '%';
self.els.scrubber.style.bottom = self._value + '%';
});
if (emitChange) {
this.emit('change', this._value);
}
},
setScrubberActive: function (active) {
window.clearTimeout(this._scrubberTimeout);
if (active) {
this.els.scrubber.classList.add('active');
} else {
this.els.scrubber.classList.remove('active');
}
},
flashScrubberActive: function () {
this.setScrubberActive(true);
var self = this;
this._scrubberTimeout = window.setTimeout(function () {
self.setScrubberActive(false);
}, 150);
},
resetInactivityTimeout: function () {
window.clearTimeout(this._inactivityTimeout);
this.show();
var self = this;
this._inactivityTimeout = window.setTimeout(function () {
self.hide();
}, this.zoomBarInactivityTimeout);
},
show: function () {
this.el.classList.add('zooming');
},
hide: function () {
this.el.classList.remove('zooming');
}
});
});