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,462 @@
/* global SpatialNavigator, SharedUtils, Applications, URL, evt, XScrollable,
KeyNavigationAdapter, ContextMenu, CardManager, PromotionList,
BookmarkManager */
(function(exports) {
'use strict';
const ICON_SIZE = 180 * (window.devicePixelRatio || 1);
const DEFAULT_ICON_PATH = 'style/icons/appic_developer.png';
/**
* @class AppDeck
*
* @requires SpatialNavigator
* @requires SharedUtils
* @requires Applications
* @requires {@link http://bit.ly/1DWJJmF|evt}
* @requires XScrollable
* @requires KeyNavigationAdapter
* @requires {@link ContextMenu}
* @requires CardManager
* @requires {@link PromotionList}
*
* @fires AppDeck#focus
*/
var AppDeck = function() {
};
AppDeck.prototype = evt({
_navigableElements: [],
_spatialNavigator: undefined,
_keyNavigationAdapter: undefined,
_contextMenu: undefined,
_focusElem: undefined,
_appDeckGridViewElem: document.getElementById('app-deck-grid-view'),
_appDeckListScrollable: undefined,
_cardManager: undefined,
_bookmarkManager: undefined,
_outOfControlArea: true,
_disableNavigation: false,
/**
* Initialize AppDeck. This is the main entry of the whole app.
*
* @public
* @method AppDeck#init
*/
init: function ad_init() {
var that = this;
this._keyNavigationAdapter = new KeyNavigationAdapter();
this._keyNavigationAdapter.init();
this._cardManager = new CardManager();
// initialize CardManager in 'readonly' mode. Notice that only smart-home
// could use 'readwrite' mode.
this._cardManager.init('readonly').then(function() {
that._cardManager.on('cardlist-changed',
that.onCardListChanged.bind(that));
});
this._bookmarkManager = BookmarkManager;
this._bookmarkManager.init(null, 'readwrite');
this._bookmarkManager.on('change', that.onBookmarkChanged.bind(that));
// Because module Applications use manifest helper to get localized app
// name. We cannot initialize Applications until l10n is ready.
// See bug 1170083.
var afterApplicationsInit = function() {
var apps = Applications.getAllAppEntries();
var appGridElements = apps.map(that._createAppGridElement.bind(that));
appGridElements.forEach(function(appGridElem) {
that._appDeckGridViewElem.appendChild(appGridElem);
});
// Add Bookmarks
var bookmarkArr = [];
that._bookmarkManager.iterate(function(bookmark) {
bookmarkArr.push(bookmark);
}).then(function() {
bookmarkArr.sort((a, b) => a.date - b.date);
bookmarkArr.forEach(bookmark => {
var elem = that._createBookmarkGridElement(bookmark);
that._appDeckGridViewElem.appendChild(elem);
that._spatialNavigator.add(elem);
});
});
// promotion list must be created before XScrollable because it creates
// elements for XScrollable.
that._promotionList = new PromotionList();
that._promotionList.init();
that._appDeckListScrollable = new XScrollable({
frameElem: 'app-deck-list-frame',
listElem: 'app-deck-list',
itemClassName: 'app-deck-list-item',
margin: 1.4
});
that._navigableElements =
SharedUtils.nodeListToArray(
document.querySelectorAll('.navigable:not(.app-banner)'))
.concat(appGridElements);
that._spatialNavigator = new SpatialNavigator(that._navigableElements);
that._keyNavigationAdapter.on('move', that.onMove.bind(that));
// All behaviors which no need to have multple events while holding the
// key should use keyup.
that._keyNavigationAdapter.on('enter-keyup', that.onEnter.bind(that));
that._spatialNavigator.on('focus', that.onFocus.bind(that));
that._spatialNavigator.on('unfocus', that.onUnfocus.bind(that));
that._appDeckListScrollable.on('focus',
that.onFocusOnAppDeckListScrollable.bind(that));
// start focusing on promotion list
that._appDeckListScrollable.focus();
that._contextMenu = new ContextMenu();
that._contextMenu.init(that);
Applications.on('install', that.onAppInstalled.bind(that));
Applications.on('update', that.onAppUpdated.bind(that));
Applications.on('uninstall', that.onAppUninstalled.bind(that));
};
navigator.mozL10n.once(function() {
Applications.init(afterApplicationsInit);
});
},
/**
* Fills element with icon which is fetched from
* [app](http://mzl.la/1DJP6oZ)
*
* @private
* @method AppDeck#_fillAppButtonIcon
* @param {DOMApplication} app - app instance where we fetch icon from,
* see [here](http://mzl.la/1DJP6oZ)
* @param {SmartButton} elem - [SmartButton](http://bit.ly/1Ld0WYX)
* instance to be filled
*/
_fillAppButtonIcon: function ad_fillAppButtonIcon(app, elem) {
Applications.getIconBlob(app.manifestURL, app.entryPoint, ICON_SIZE,
function(blob) {
var iconURL = blob ? URL.createObjectURL(blob) : DEFAULT_ICON_PATH;
if (elem.dataset.revokableURL) {
// make sure to revoke iconURL once it is no longer needed.
// For example, icon is changed or app is uninstalled
URL.revokeObjectURL(elem.dataset.revokableURL);
}
elem.dataset.revokableURL = blob ? iconURL : undefined;
elem.style.backgroundImage = 'url("' + iconURL + '")';
});
},
/**
* Create SmartButton element based on input [app](http://mzl.la/1DJP6oZ)
* instance
*
* @private
* @method AppDeck#_createAppGridElement
* @param {DOMApplication} app - [app](http://mzl.la/1DJP6oZ|)
* instance
* @return {SmartButton} - see [here](http://bit.ly/1Ld0WYX)
*/
_createAppGridElement: function ad_createAppGridElement(app) {
var appButton = document.createElement('smart-button');
appButton.dataset.manifestURL = app.manifestURL;
appButton.dataset.entryPoint = app.entryPoint;
appButton.dataset.name = app.name;
appButton.dataset.removable = app.removable;
appButton.setAttribute('type', 'app-button');
appButton.setAttribute('app-type', 'app');
appButton.classList.add('app-button');
appButton.classList.add('navigable');
appButton.setAttribute('label', app.name);
this._fillAppButtonIcon(app, appButton);
return appButton;
},
_createBookmarkGridElement: function ad_createBookmarkElement(bookmark) {
var bookmarkButton = document.createElement('smart-button');
bookmarkButton.dataset.url = bookmark.url;
bookmarkButton.dataset.name = bookmark.name;
bookmarkButton.dataset.removable = true;
bookmarkButton.setAttribute('type', 'app-button');
bookmarkButton.setAttribute('app-type', 'bookmark');
bookmarkButton.classList.add('app-button');
bookmarkButton.classList.add('navigable');
bookmarkButton.setAttribute('label', bookmark.name);
if(bookmark.icon) {
var iconURL = URL.createObjectURL(bookmark.icon);
bookmarkButton.dataset.revokableURL = iconURL;
bookmarkButton.style.backgroundImage = 'url("' + iconURL + '")';
}
return bookmarkButton;
},
onCardListChanged: function ad_onCardListChanged() {
if (this._focusElem) {
this.fireFocusEvent(this._focusElem);
}
},
onBookmarkChanged: function ad_onBookmarkChanged(evt) {
var targetElem;
switch (evt.operation) {
case 'added':
this._bookmarkManager.get(evt.id).then(bookmark => {
targetElem = this._createBookmarkGridElement(bookmark);
this._appDeckGridViewElem.appendChild(targetElem);
this._spatialNavigator.add(targetElem);
});
break;
case 'removed':
targetElem = this._appDeckGridViewElem.querySelector(
'smart-button[data-url="' + evt.id +'"]');
// Move focus to next or previous element of `elem`, because
// we are going to remove `elem` from DOM tree
var nextFocus = targetElem.nextElementSibling ||
targetElem.previousElementSibling;
this._spatialNavigator.focus(nextFocus);
this._appDeckGridViewElem.removeChild(targetElem);
this._spatialNavigator.remove(targetElem);
URL.revokeObjectURL(targetElem.dataset.revokableURL);
break;
}
},
/**
* Notify other modules about details of currently focused element.
*
* @public
* @method AppDeck#fireFocusEvent
* @param {HTMLElement} elem - currently focused element
*/
/**
* This event is fired whenever the focus in AppDeck moves to a pinnable
* element (For now it's an app or a bookmark).
* @event AppDeck#focus
* @type {Object}
* @property {HTMLElement} elem - currently focused element
* @property {Boolean} pinned - Has it been pinned to Home
*/
fireFocusEvent: function ad_fireFocusEvent(elem) {
var that = this;
var type = elem && elem.getAttribute('app-type');
var query;
if (type === 'app') {
query = {
manifestURL: elem.dataset.manifestURL,
entryPoint: elem.dataset.entryPoint
};
} else if (type === 'bookmark') {
query = {
url: elem.dataset.url
};
} else {
// We have no other types for now.
return;
}
this._cardManager.isPinned(query).then(pinned => {
that.fire('focus', {
elem: elem,
pinned: pinned
});
});
},
onFocus: function ad_onFocus(elem) {
// The _focusElement may be XScrollable. It doesn't have blur method. If
// it has blur method, it should be DOM element.
if (this._focusElem && this._focusElem.blur) {
this._focusElem.blur();
}
if (elem instanceof XScrollable) {
// When we move focus from smart-button to scrollable,
// we have to remember the last focused smart-button in app list. So
// that, we can move focus back to app-deck-grid-view area afterwards.
if (this._focusElem) {
if (this._focusElem.nodeName === 'SMART-BUTTON') {
this._lastFocusedSmartButton = this._focusElem;
}
}
this._focusElem = elem;
elem.focus();
} else if (elem.nodeName) {
// When we move focus back to app-deck-grid-view area
// (e.g. area contains smart-button), we should always focus on
// last focused smart-button if there is any
if (elem.nodeName === 'SMART-BUTTON' && this._lastFocusedSmartButton) {
elem = this._lastFocusedSmartButton;
this._spatialNavigator.focus(elem);
}
elem.focus();
// Locate focused button to vertical middle of grid view. This way
// buttons one row above would always slightly be on bottom of the
// topmost scroll list (accroding to our layout). Thus we can prevent
// jumping from second row to the scroll list when pressing up key.
this._scrollTo(elem);
this._focusElem = elem;
}
this.fireFocusEvent(elem);
},
_scrollTo: function ad_scrollTo(elem) {
var scrollY = (elem.offsetTop - this._appDeckGridViewElem.offsetTop) -
(this._appDeckGridViewElem.offsetHeight - elem.offsetHeight) / 2;
this._appDeckGridViewElem.scrollTo(0, scrollY);
},
onUnfocus: function ad_onUnfocus(elem) {
// When we move focus from smart-button to another smart-button,
// we don't have to remember last focused smart-button
if (elem.nodeName === 'SMART-BUTTON') {
this._lastFocusedSmartButton = undefined;
}
},
onFocusOnAppDeckListScrollable:
function ad_onFocusOnAppDeckListScrollable(scrollable, itemElem) {
itemElem.focus();
},
onEnter: function ad_onEnter() {
if (this._disableNavigation) {
return;
}
var focused = this._spatialNavigator.getFocusedElement();
if (focused && focused.dataset && focused.dataset.manifestURL) {
Applications.launch(
focused.dataset.manifestURL, focused.dataset.entryPoint);
} else if (focused && focused.dataset && focused.dataset.url) {
window.open(focused.dataset.url, '_blank', 'remote=true,applike=true');
}
},
onMove: function ad_onMove(key) {
if (this._disableNavigation) {
return;
}
// When we are not in the SpatialNavigator's control area,
// use XScrollable object's spatial navigator to move.
// If it fails to move, and it's a 'down' operation, then we
// move into the last focused element within SpatialNavigator's
// control area.
if (this._outOfControlArea) {
if (!this._appDeckListScrollable.spatialNavigator.move(key) &&
key == 'down') {
this._spatialNavigator.focus(this._lastFocusedSmartButton);
this._outOfControlArea = false;
}
} else {
// Vice versa.
if (!this._spatialNavigator.move(key) && key == 'up') {
this._appDeckListScrollable.focus();
this._outOfControlArea = true;
}
}
},
onAppInstalled: function ad_onAppInstalled(apps) {
var that = this;
var appGridElements = apps.map(this._createAppGridElement.bind(this));
var firstBookmarkElem = this._appDeckGridViewElem.querySelector(
'smart-button[app-type="bookmark"]');
appGridElements.forEach(function(elem) {
that._appDeckGridViewElem.insertBefore(elem, firstBookmarkElem);
that._spatialNavigator.add(elem);
});
},
onAppUpdated: function ad_onAppUpdated(apps) {
var that = this;
var appGridElements = apps.map(this._findAppGridElement.bind(this));
appGridElements.forEach(function(elem, index) {
var app = apps[index];
elem.dataset.name = app.name;
that._fillAppButtonIcon(app, elem);
});
},
onAppUninstalled: function ad_onAppUninstalled(apps) {
var that = this;
var appGridElements = apps.map(this._findAppGridElement.bind(this));
appGridElements.forEach(function(elem) {
if (elem.dataset.revokableURL) {
URL.revokeObjectURL(elem.dataset.revokableURL);
}
// Move focus to next or previous element of `elem`, because
// we are going to remove `elem` from DOM tree
var nextFocus = elem.nextElementSibling || elem.previousElementSibling;
that._spatialNavigator.focus(nextFocus);
that._spatialNavigator.remove(elem);
that._appDeckGridViewElem.removeChild(elem);
});
},
/**
* Shorthand function to get all SmartButtons of apps
*
* @private
* @method AppDeck#_findAllAppGridElements
* @return {Array} array of SmartButton
*/
_findAllAppGridElements: function ad_findallAppGridElements() {
return SharedUtils.nodeListToArray(
document.getElementsByClassName('app-button'));
},
/**
* Shorthand function to find specific SmartButton representing the app
*
* @private
* @method AppDeck#_findAppGridElement
* @param {DOMApplication} app the [app](http://mzl.la/1DJP6oZ) instance
* which we want to find SmartButton is
* represented for
* @return {SmartButton}
*/
_findAppGridElement: function ad_findAppGridElement(app) {
var elements = this._findAllAppGridElements();
var found;
elements.some(function(element) {
if (element.dataset.manifestURL === app.manifestURL) {
found = element;
return true;
}
return false;
});
return found;
},
disableNavigation: function ad_stopNavigation() {
this._disableNavigation = true;
},
enableNavigation: function ad_stopNavigation() {
this._disableNavigation = false;
this._spatialNavigator.focus();
}
});
exports.appDeck = new AppDeck();
exports.appDeck.init();
}(window));

View file

@ -0,0 +1,252 @@
'use strict';
/* global MozActivity, Applications, BookmarkManager, SmartModalDialog */
/* jshint nonew: false */
(function(exports) {
/**
* It handles everything related to contextmenu in app-deck.
*
* @class ContextMenu
* @requires Applications
* @requires SharedUtils
*/
var ContextMenu = function() {
};
ContextMenu.prototype = {
pinToHomeElem: document.getElementById('pin-to-home'),
unpinFromHomeElem: document.getElementById('unpin-from-home'),
mainSection: document.getElementById('main-section'),
removeElem: document.getElementById('remove-card'),
contextMenuElem: document.getElementById('context-menu'),
_appDeck: undefined,
_target: {},
/**
* Initialize ContextMenu
*
* @public
* @method ContextMenu#init
* @param {AppDeck} appDeck - instance of {@link AppDeck}
*/
init: function cm_init(appDeck) {
this._appDeck = appDeck;
this._appDeck.on('focus', this.onFocus.bind(this));
this._bookmarkManager = BookmarkManager;
this._modalDialog = new SmartModalDialog();
this.pinToHomeElem.addEventListener('click', this.pinOrUnpin.bind(this));
this.unpinFromHomeElem.addEventListener(
'click', this.pinOrUnpin.bind(this));
this.removeElem.addEventListener('click', this.onRemove.bind(this));
this._modalDialog.container.addEventListener('modal-dialog-closed',
this._appDeck.enableNavigation.bind(this._appDeck));
this._modalDialog.container.addEventListener('modal-dialog-will-open',
this._appDeck.disableNavigation.bind(this._appDeck));
},
_selfApp: undefined,
/**
* Underlying function which actual send string message on 'appdeck-channel'
*
* @private
* @method ContextMenu#_connectAndSend
* @param {String} message - the message which will be sent on
* 'appdeck-channel'
*/
_connectAndSend: function cm_sendMessage(message) {
this._selfApp.connect('appdeck-channel').then(function (ports) {
ports.forEach(function(port) {
port.postMessage(message);
});
});
},
/**
* Send message on 'appdeck-channel' using Inter-App Communication.
* smart-home will monitor this channel. Currently we use this channel to
* tell smart-home to what app we would like to unpin.
*
* @public
* @method ContextMenu#sendMessage
* @param {String} type - message type, which must be 'unpin' for now.
* @param {Object} data - message data in JSON object format
*/
sendMessage: function cm_sendMessage(type, data) {
var that = this;
var message = {
type: type,
data: data
};
if (this._selfApp) {
this._connectAndSend(message);
} else {
navigator.mozApps.getSelf().onsuccess = function(evt) {
that._selfApp = evt.target.result;
that._connectAndSend(message);
};
}
},
/**
* To tell smart-home what app we would like to unpin
*
* @private
* @method ContextMetnu#_sendUnpinMessage
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
*/
_sendUnpinMessage: function cm_sendUnpinMessage(cardElem) {
var type = cardElem.getAttribute('app-type');
if (type === 'app') {
// Notice that here we didn't specify launchURL because we assume all
// 'Application' in app-deck are launched by calling app.launch().
// However, in the case of other deck-like app, if we are going to pin
// application which is launched by different launchURL, we must specify
// 'launchURL' here.
this.sendMessage('unpin', {
manifestURL: cardElem.dataset.manifestURL
});
} else if (type === 'bookmark') {
this.sendMessage('unpin', {
url: cardElem.dataset.url
});
}
},
/**
* Based on currently focused SmartButton, we'll decide we are going to
* pin or unpin the app. This function is invoked when we get 'click' event
* on pinToHomeElem. Notice that we use `MozActivity` to pin app because we
* want to bring users to Home when they finish pinning. However we unpin
* app via IAC because we want users stay in AppDeck after unpinning.
*
* @public
* @method ContextMenu#pinOrUnpin
*/
pinOrUnpin: function cm_pinOrUnpin() {
var cardElem = this._target.elem;
var type = this._getTargetType();
var dataset = cardElem.dataset;
if (this._target.pinned) {
this._sendUnpinMessage(this._target.elem);
} else if (type === 'app') {
// XXX: preferredSize should be determined by
// real offsetWidth of cardThumbnailElem in smart-home instead of
// hard-coded value
Applications.getIconBlob(
dataset.manifestURL, dataset.entryPoint, 354, blob => {
// Notice that here we didn't specify launchURL because we assume
// all 'Application' in app-deck are launched by calling
// app.launch().
// However, in the case of other deck-like app, if we are going to
// pin application which is launched by different launchURL, we
// must specify 'launchURL' here.
new MozActivity({
name: 'pin',
data: {
type: 'Application',
group: 'application',
manifestURL: dataset.manifestURL,
// We use app's original icon instead of screenshot here
// because we are in app deck. For the case of getting
// screenshot, please refer to bug 1100238.
thumbnail: blob
}
});
});
} else if (type === 'bookmark') {
this._bookmarkManager.get(dataset.url).then(card => {
new MozActivity({
name: 'pin',
data: {
name: {raw: card.name},
type: 'AppBookmark',
group: 'application',
url: card.url,
thumbnail: card.icon
}
});
});
}
},
onRemove: function cm_onRemove() {
var type = this._getTargetType();
var elem = this._target.elem;
if (type === 'app') {
this.uninstall(elem);
} else if (type === 'bookmark') {
this._appDeck.disableNavigation();
this._modalDialog.open({
message: {
textL10nId: {
id: 'delete-bookmark-alert',
args: {
name: elem.getAttribute('label')
}
}
},
buttonSettings: [
{
textL10nId: 'cancel',
defaultFocus: true,
},
{
textL10nId: 'delete',
class: 'danger',
onClick: () => {
this._bookmarkManager.remove(elem.dataset.url);
this._sendUnpinMessage(elem);
}
}
]
});
}
},
_getTargetType: function cm_getType() {
return this._target.elem && this._target.elem.getAttribute('app-type');
},
/**
* Uninstall app represented by current focused SmartButton
* @public
* @method ContextMenu#uninstall
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
*/
uninstall: function cm_uninstall(cardElem) {
var appInstance =
Applications.installedApps[cardElem.dataset.manifestURL];
if (appInstance) {
navigator.mozApps.mgmt.uninstall(appInstance);
}
},
onFocus: function cm_onFocus(target) {
this._target = target;
this.contextMenuElem.innerHTML = '';
if (target.pinned) {
this.contextMenuElem.appendChild(this.unpinFromHomeElem);
} else {
this.contextMenuElem.appendChild(this.pinToHomeElem);
}
this.contextMenuElem.appendChild(this.removeElem);
}
};
exports.ContextMenu = ContextMenu;
})(window);

View file

@ -0,0 +1,213 @@
/* global MozActivity, KeyEvent */
(function(exports) {
'use strict';
const ANIMATION_TIMER = 400;
var TEST_LIST = [
{
'imageUrl': 'style/banners/firefox-os.png',
'title': 'Firefox OS',
'description': 'Learn more about Firefox OS.',
'url': 'https://www.mozilla.org/en-US/firefox/os/'
}, {
'imageUrl': 'style/banners/participation.png',
'title': 'Firefox OS Smart TV Participation',
'description': 'Learn how you can participate!',
'url': 'https://firefoxos.mozilla.community/fxos-tv/'
}, {
'imageUrl': 'style/banners/mdn.png',
'title': 'MDN',
'description': 'Learn about TV apps developement!',
'url': 'http://mzl.la/1NFslRZ'
}
];
/**
* PromotionList controls everything on the upper half of AppDeck
* @class PromotionList
*/
function PromotionList() {}
var proto = PromotionList.prototype;
proto.init = function() {
this.bannerContainer = document.getElementById('app-deck-list');
this.container = document.getElementById('app-deck-main-banner');
this.animator = document.getElementById('app-deck-animation-banner');
this.animator.addEventListener('transitionend', this);
this._initBannerSize(this.animator);
this._initBannerSize(this.container);
this.animator.classList.add('hidden');
// load test content.
TEST_LIST.forEach(this._createBanner.bind(this));
// set first one as context item.
this.setContextItem(0);
};
proto._createBanner = function(item, index) {
var div = document.createElement('div');
div.classList.add('app-banner');
var btn = document.createElement('smart-button');
btn.setAttribute('type', 'banner-button');
btn.classList.add('app-deck-list-item');
btn.style.backgroundImage = 'url(\'' + item.imageUrl + '\'), ' +
'url(\'style/icons/ic_default_app.png\')';
btn.dataset.index = index;
btn.addEventListener('focus', this);
btn.addEventListener('keyup', this);
div.appendChild(btn);
this.bannerContainer.appendChild(div);
};
proto._initBannerSize = function pl_initBannerSize(container) {
var bg = container.querySelector('.banner-background');
var text = container.querySelector('.banner-text-container');
text.style.width = bg.style.width = container.clientWidth + 'px';
};
proto._fillMainBanner = function pl_fillMainBanner(container, item) {
var bg = container.querySelector('.banner-background');
var title = container.querySelector('.banner-title');
var desc = container.querySelector('.banner-description');
bg.style.backgroundImage = 'url(\'' + item.imageUrl + '\')';
title.textContent = item.title;
desc.textContent = item.description;
};
proto._slideItemIn = function pl_slideItemIn(item) {
// fill animation and start to slide
this._fillMainBanner(this.animator, item);
// cancel previous one if we already have it.
this._cancelSliding();
// remove hidden and add standby to prepare for the startup of animation.
this.animator.classList.remove('hidden');
this.animator.classList.add('standby');
this._animationTimeout = setTimeout(function() {
this._contextItem = item;
this.animationTimeout = 0;
this.animator.classList.remove('standby');
this.animator.classList.add('sliding');
this.container.classList.add('sliding');
}.bind(this), ANIMATION_TIMER);
};
proto._cancelSliding = function pl_cancelSliding() {
if (this._animationTimeout) {
clearTimeout(this._animationTimeout);
this._animationTimeout = 0;
// If _animationTimeout is not 0, it means the animation is prepared but
// is not triggered. So, we need to rollback the state while clear the
// timer.
this.animator.classList.remove('standby');
this.animator.classList.add('hidden');
}
};
proto.setContextItem = function pl_setContextItem(idx) {
var item = TEST_LIST[idx];
if (!this._contextItem) {
// If context item is undefined or null, it's first context item, we just
// set it without animation.
this._contextItem = item;
this._fillMainBanner(this.container, item);
return;
}
if (this._contextItem === item) {
// If the context item is self, we don't need to slide anything and should
// clear all pending or sliding timer.
if (this.animator.classList.contains('sliding')) {
// If _contextItem is sliding now, we should clear the pending animation
// which means user presses key back and forward quickly.
this._pendingAnimationItem = null;
} else {
// We still need to clear the sliding timer because the waiting item may
// not be current item.
this._cancelSliding();
}
return;
}
if (this.animator.classList.contains('sliding')) {
this._pendingAnimationItem = item;
return;
}
this._slideItemIn(item);
};
proto._handleFocusChange = function(evt) {
if (evt.target.dataset.index === null ||
typeof(evt.target.dataset.index) === 'undefined') {
return;
}
this.setContextItem(evt.target.dataset.index);
};
proto._openLink = function(evt) {
var idx = evt.target.dataset.index;
var url = TEST_LIST[idx].url;
// Ask browser app to open this link.
var req = new MozActivity({
name: 'view',
data: {type: 'url', url: url}
});
req.onerror = function() {
console.log(req.error.name);
};
};
proto._handleTransitionEnd = function(evt) {
if (evt.propertyName !== 'transform' ||
evt.currentTarget !== this.animator) {
return;
}
// banner-background is the last one to move. Once it is moved, we should
// set all data to main container.
if (evt.target.classList.contains('banner-background')) {
this.animator.classList.add('hidden');
this.animator.classList.remove('sliding');
this.container.classList.remove('sliding');
this._fillMainBanner(this.container, this._contextItem);
if (this._pendingAnimationItem) {
this._slideItemIn(this._pendingAnimationItem);
this._pendingAnimationItem = null;
}
}
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'focus':
this._handleFocusChange(evt);
break;
case 'transitionend':
this._handleTransitionEnd(evt);
break;
case 'keyup':
if (evt.keyCode != KeyEvent.DOM_VK_RETURN) {
return;
}
this._openLink(evt);
break;
}
};
exports.PromotionList = PromotionList;
})(window);