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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
377
gaia/apps/camera/js/views/preview-gallery.js
Executable file
377
gaia/apps/camera/js/views/preview-gallery.js
Executable file
|
|
@ -0,0 +1,377 @@
|
|||
/* jshint -W098 */
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
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');
|
||||
|
||||
/**
|
||||
* `<gaia-header>` used in template
|
||||
*/
|
||||
|
||||
require('gaia-header');
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
var SWIPE_DISTANCE_THRESHOLD = window.innerWidth / 3; // pixels
|
||||
var SWIPE_VELOCITY_THRESHOLD = 1.0; // pixels/ms
|
||||
|
||||
var SWIPE_DURATION = 150; // How long to animate the swipe
|
||||
var FADE_IN_DURATION = 450; // How long to animate the fade in after swipe
|
||||
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
|
||||
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');
|
||||
|
||||
// We're appending new elements to DOM so to make sure headers are
|
||||
// properly resized and centered, we emmit a lazyload event.
|
||||
// This will be removed when the gaia-header web component lands.
|
||||
window.dispatchEvent(new CustomEvent('lazyload', {
|
||||
detail: this.el
|
||||
}));
|
||||
|
||||
// Configure the MediaFrame component
|
||||
this.configure();
|
||||
|
||||
// Clean up
|
||||
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);
|
||||
// Our screen reader synthesizes wheel events when the user swipes with two
|
||||
// fingers in either direction. Those events always have their deltaMode set
|
||||
// to DOM_DELTA_PAGE, and the delta value can either be -1, 0 or 1.
|
||||
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 the video is playing, switch to preview.
|
||||
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) {
|
||||
// If this is not the end of the gesture, then just move the media
|
||||
// frame left or right by the specified amount.
|
||||
this.els.frameContainer.style.transform =
|
||||
'translate(' + swipeAmount + 'px, 0)';
|
||||
return;
|
||||
}
|
||||
|
||||
// If we were passed a velocity, then this is the end of the gesture
|
||||
// and we have to figure out whether we are going to go to the
|
||||
// next item, the previous item, or remain on the current item.
|
||||
var direction, translation;
|
||||
|
||||
// Should we move to the previous item?
|
||||
if (swipeAmount > 0 && swipeVelocity > 0 && // Same sign and
|
||||
this.currentIndex > 1 && // has previous item and
|
||||
(swipeAmount > SWIPE_DISTANCE_THRESHOLD || // distance big enough or
|
||||
swipeVelocity > SWIPE_VELOCITY_THRESHOLD)) // speed fast enough
|
||||
{
|
||||
direction = 'right';
|
||||
translation = '100%';
|
||||
}
|
||||
// Should we move to the next item?
|
||||
else if (swipeAmount <= 0 && swipeVelocity <= 0 &&
|
||||
this.currentIndex < this.lastIndex &&
|
||||
(swipeAmount < -SWIPE_DISTANCE_THRESHOLD ||
|
||||
swipeVelocity < -SWIPE_VELOCITY_THRESHOLD)) {
|
||||
direction = 'left';
|
||||
translation = '-100%';
|
||||
}
|
||||
|
||||
// If we're not moving either left or right, just animate the
|
||||
// item to its starting point and we're done.
|
||||
if (!direction) {
|
||||
animate('transform', 'translate(0,0)', SWIPE_DURATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, we are going to slide the current item off the screen
|
||||
// and display the next or previous item.
|
||||
|
||||
// First, stop the video if there is one and it is playing.
|
||||
if (this.videoPlaying) {
|
||||
this.onVideoPaused();
|
||||
}
|
||||
|
||||
// Now animate the item off the screen
|
||||
animate('transform', 'translate(' + translation + ', 0)',
|
||||
SWIPE_DURATION,
|
||||
function() {
|
||||
// Once we're off screen ask the controller to
|
||||
// switch to the new image.
|
||||
self.emit('swipe', direction);
|
||||
|
||||
// On the next redraw, put the frame back on the screen
|
||||
// but make it opaque
|
||||
window.requestAnimationFrame(function() {
|
||||
// Next, make the frame invisible
|
||||
// and put it back on the screen
|
||||
self.els.frameContainer.style.opacity = 0;
|
||||
self.els.frameContainer.style.transform = 'translate(0,0)';
|
||||
|
||||
// Then on the frame after that, animate the opacity to
|
||||
// make it visible again.
|
||||
window.requestAnimationFrame(function() {
|
||||
animate('opacity', 1, FADE_IN_DURATION);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// A helper function to animate the specified CSS property to the
|
||||
// specified value for the specified duration. When the animation
|
||||
// is complete, reset everything and call the done callback
|
||||
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;
|
||||
|
||||
// Ensure that the information about the number of thumbnails is provided to
|
||||
// the screen reader.
|
||||
this.els.mediaFrame.setAttribute('data-l10n-id', 'media-frame');
|
||||
this.els.mediaFrame.setAttribute('data-l10n-args', JSON.stringify(
|
||||
{ total: total, current: current }));
|
||||
},
|
||||
|
||||
onResize: function() {
|
||||
// And we have to resize the frame (and its video player)
|
||||
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');
|
||||
|
||||
// We add the event listner for menu items and cancel buttons
|
||||
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>';
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue