Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Bug 807529 - Gaia homescreen app: do not wait for mozApps to start pa…

…inting. r=vingtetun
  • Loading branch information...
commit 56b368723178521be7f6d417e7944fa7b909d1a8 1 parent f94a5dd
@philikon philikon authored vingtetun committed
View
1  .gitignore
@@ -9,6 +9,7 @@ manifest.appcache
/apps/system/camera
/apps/settings/resources/gaia_commit.txt
/apps/homescreen/js/init.json
+/apps/homescreen/js/hiddenapps.js
/apps/costcontrol/js/config.json
/apps/sms/js/blacklist.json
/test_apps/test-agent/test/config.json
View
12 apps/communications/manifest.webapp
@@ -12,8 +12,8 @@
"launch_path": "/dialer/index.html#keyboard-view",
"name": "Phone",
"icons": {
- "120": "/style/icons/Dialer.png",
- "60": "/style/icons/60/Dialer.png"
+ "120": "/dialer/style/icons/Dialer.png",
+ "60": "/dialer/style/icons/60/Dialer.png"
},
"locales": {
"ar": {
@@ -38,8 +38,8 @@
"launch_path": "/contacts/index.html",
"name": "Contacts",
"icons": {
- "120": "/style/icons/Contacts.png",
- "60": "/style/icons/60/Contacts.png"
+ "120": "/contacts/style/icons/Contacts.png",
+ "60": "/contacts/style/icons/60/Contacts.png"
},
"locales": {
"ar": {
@@ -80,10 +80,6 @@
"wifi":{},
"time": {}
},
- "icons": {
- "120": "/dialer/style/icons/Dialer.png",
- "60": "/dialer/style/icons/60/Dialer.png"
- },
"orientation": "portrait-primary",
"activities": {
"pick": {
View
2  apps/homescreen/everything.me/js/Brain.js
@@ -767,7 +767,7 @@ Evme.Brain = new function() {
this.appRedirectExecute = function(data){
var appIcon = Evme.Utils.formatImageData(data.icon);
if (data.installed) {
- Applications.launch(data.appUrl, '');
+ GridManager.getAppByOrigin(data.appUrl).launch();
} else {
Evme.Utils.getRoundIcon(appIcon, 49, function(appIcon) {
Evme.Utils.sendToFFOS(Evme.Utils.FFOSMessages.APP_CLICK, {
View
74 apps/homescreen/everything.me/js/etmmanager.js
@@ -6,7 +6,7 @@ var EvmeManager = (function() {
function openApp(params) {
var evmeApp = new EvmeApp({
- url: params.originUrl,
+ bookmarkURL: params.originUrl,
name: params.title,
icon: params.icon
});
@@ -18,21 +18,11 @@ var EvmeManager = (function() {
}
function addBookmark(params) {
- var data = {
- url: params.originUrl,
+ GridManager.install({
+ bookmarkURL: params.originUrl,
name: params.title,
icon: params.icon
- }
-
- function success() {
- Applications.installBookmark(new Bookmark(data));
- }
-
- function error() {
- // Anything to do in case of error?
- }
-
- HomeState.saveBookmark(data, success, error);
+ });
}
function openUrl(url) {
@@ -61,14 +51,29 @@ var EvmeManager = (function() {
}
function getApps() {
- return Applications.getAll();
+ return GridManager.getApps();
}
-
+
function getAppIcon(app) {
- return Applications.getIcon(app.origin);
+ var iconsForApp = GridManager.getIconsForApp(app);
+ for (var entryPoint in iconsForApp) {
+ return iconsForApp[entryPoint].descriptor.icon;
+ }
}
function getAppName(app) {
- return Applications.getName(app.origin);
+ var manifest = app.manifest;
+ if (!manifest) {
+ return null;
+ }
+
+ if ('locales' in manifest) {
+ var locale = manifest.locales[document.documentElement.lang];
+ if (locale && locale.name) {
+ return locale.name;
+ }
+ }
+
+ return manifest.name;
}
return {
@@ -77,7 +82,7 @@ var EvmeManager = (function() {
addBookmark: addBookmark,
isAppInstalled: function isAppInstalled(url) {
- return Applications.isInstalled(url);
+ return !!GridManager.getAppByOrigin(url);
},
getApps: getApps,
getAppIcon: getAppIcon,
@@ -96,3 +101,34 @@ var EvmeApp = function createEvmeApp(params) {
};
extend(EvmeApp, Bookmark);
+
+EvmeApp.prototype.launch = function evmeapp_launch(url, name) {
+ var features = {
+ name: this.manifest.name.replace(/\s/g, ' '),
+ icon: this.manifest.icons['60']
+ };
+
+ if (!GridManager.getIconForBookmark(this.origin)) {
+ features.origin = {
+ name: features.name,
+ url: encodeURIComponent(this.origin)
+ };
+ }
+
+ if (url && url !== this.origin && !GridManager.getIconForBookmark(url)) {
+ var searchName = navigator.mozL10n.get('wrapper-search-name', {
+ topic: name,
+ name: this.manifest.name
+ }).replace(/\s/g, ' ');
+
+ features.name = searchName;
+ features.search = {
+ name: searchName,
+ url: encodeURIComponent(url)
+ };
+ }
+
+ // The third parameter is received in window_manager without whitespaces
+ // so we decice replace them for  
+ return window.open(url || this.origin, '_blank', JSON.stringify(features));
+};
View
1  apps/homescreen/index.html
@@ -35,6 +35,7 @@
<script type="text/javascript" defer src="everything.me/js/everything.me.js"></script>
<script type="text/javascript" defer src="js/homescreen.js"></script>
<script type="text/javascript" defer src="js/wallpaper.js"></script>
+ <script type="text/javascript" defer src="js/hiddenapps.js"></script>
<link rel="resource" type="application/l10n" href="locales/locales.ini">
<link rel="resource" type="application/l10n" href="shared/locales/date.ini">
View
377 apps/homescreen/js/appmanager.js
@@ -1,377 +0,0 @@
-
-'use strict';
-
-var ApplicationMock = function(app, launchPath, alternativeOrigin) {
- this.app = app;
- this.entry_point = launchPath;
- this.origin = alternativeOrigin;
- //Clone the manifest
- this.manifest = {};
- for (var field in app.manifest) {
- this.manifest[field] = app.manifest[field];
- }
-
- var entryPoint = app.manifest.entry_points[launchPath];
- this.manifest.name = entryPoint.name;
- this.manifest.launch_path = entryPoint.launch_path;
- this.manifest.icons = entryPoint.icons;
- this.manifest.origin = alternativeOrigin;
-
- this.manifestURL = app.manifestURL;
- this.receipts = app.receipts;
- this.installOrigin = app.installOrigin;
- this.installTime = app.installTime;
-
- this.manifest.use_manifest = true;
-};
-
-ApplicationMock.prototype = {
- launch: function _launch(startPoint) {
- this.app.launch(this.entry_point);
- },
-
- uninstall: function _uninstall() {
- this.app.uninstall();
- }
-};
-
-var Applications = (function() {
- var installedApps = {};
-
- var callbacks = [], ready = false;
-
- var installer = navigator.mozApps.mgmt;
- installer.getAll().onsuccess = function onSuccess(e) {
- var apps = e.target.result;
- apps.forEach(function parseApp(app) {
- var manifest = app.manifest;
- if (!manifest) {
- return;
- }
-
- // and add a fake app object for each one.
- var entryPoints = manifest.entry_points;
- if (!entryPoints) {
- installedApps[app.origin] = app;
- return;
- }
-
- for (var launchPath in entryPoints) {
- if (!entryPoints[launchPath].hasOwnProperty('icons'))
- continue;
-
- var alternativeOrigin = app.origin + '/' + launchPath;
- var newApp = new ApplicationMock(app, launchPath, alternativeOrigin);
- installedApps[alternativeOrigin] = newApp;
- }
- });
-
- ready = true;
-
- callbacks.forEach(function(callback) {
- if (callback.type == 'ready') {
- callback.callback(installedApps);
- }
- });
- };
-
- installer.onuninstall = function uninstall(event) {
- var app = event.application;
- delete installedApps[app.origin];
-
- callbacks.forEach(function(callback) {
- if (callback.type == 'uninstall') {
- callback.callback(app);
- }
- });
- };
-
- installer.oninstall = function install(event) {
- var app = event.application;
- if (installedApps[app.origin])
- return;
-
- installedApps[app.origin] = app;
-
- var fireCallbacks = function() {
- callbacks.forEach(function(callback) {
- if (callback.type == 'install') {
- callback.callback(app);
- }
- });
- }
-
- var icon = getIcon(app.origin);
-
- // No need to put data: URIs in the cache
- if (!icon || icon.indexOf('data:') != -1) {
- fireCallbacks();
- return;
- }
-
- // Download the application icon and assign it as an attribute of
- // the manifest. As a side effect the icon will be store in the
- // application database. Should it be an explicit method instead?
- var xhr = new XMLHttpRequest({mozSystem: true});
- xhr.open('GET', icon, true);
- xhr.responseType = 'blob';
- xhr.send(null);
-
- xhr.onreadystatechange = function saveIcon_readyStateChange(evt) {
- if (xhr.readyState != 4) {
- return;
- }
-
- if (xhr.status == 0 || xhr.status == 200) {
- var fileReader = new FileReader();
- fileReader.onload = function fileReader_load(evt) {
- cacheIcon(app.origin, evt.target.result);
- fireCallbacks();
- }
- fileReader.readAsDataURL(xhr.response);
- } else {
- // 404 is not an error is the xhr world, so let's make sure
- // the application is shown on the homescreen even if the icon
- // does not appears.
- fireCallbacks();
- }
- }
-
- xhr.onerror = function saveIcon_onerror() {
- fireCallbacks();
- }
- };
-
- /*
- * Returns all installed applications
- */
- function getAll() {
- var applications = [];
- for (var app in installedApps) {
- applications.push(installedApps[app]);
- }
- return applications;
- };
-
- function addEventListener(type, callback) {
- callbacks.push({ type: type, callback: callback });
- };
-
- // Look up the app object for a specified app origin
- function getByOrigin(origin) {
- var app = installedApps[origin];
- if (app) {
- return app;
- }
-
- // Trailing '/'
- var trimmedOrigin = origin.slice(0, origin.length - 1);
- return installedApps[trimmedOrigin];
- };
-
- /*
- * Returns installed apps
- */
- function getInstalledApplications() {
- var ret = {};
-
- for (var i in installedApps) {
- ret[i] = installedApps[i];
- }
-
- return ret;
- };
-
- /*
- * Returns the origin for an apllication
- *
- * {Object} Moz application
- *
- */
- function getOrigin(app) {
- return app.origin;
- };
-
- /*
- * Returns the manifest that describes the app
- *
- * {String} App origin
- *
- */
- function getManifest(origin) {
- var app = getByOrigin(origin);
- return app ? app.manifest : null;
- };
-
- function cacheIcon(origin, icon) {
- var manifest = getManifest(origin);
- if (manifest && icon) {
- manifest._icon = icon;
- }
- };
-
- /*
- * Returns true if it's a core application
- *
- * {Object} Moz application
- *
- */
- function isCore(app) {
- return !app.removable;
- };
-
- var deviceWidth = document.documentElement.clientWidth;
-
- /*
- * Returns the preferred size of the icon
- *
- * {Array} All sizes of the icon
- * {Number} Preferred icon size
- *
- */
- function getPreferredSize(icons, preferredSize) {
- var result = Number.MAX_VALUE;
- var max = 0;
-
- Object.keys(icons).forEach(function(str) {
- var size = parseInt(str, 10);
- if (size > max)
- max = size;
-
- if (size >= preferredSize && size < result)
- result = size;
- });
- // If there is an icon matching the preferred size, we return the result,
- // if there isn't, we will return the maximum available size.
- return (result === Number.MAX_VALUE) ? max : result;
- }
-
- /*
- * Returns an icon given an origin
- *
- * {String} App origin
- *
- */
- function getIcon(origin) {
- var manifest = getManifest(origin);
- if (!manifest) {
- return null;
- }
-
- if ('_icon' in manifest) {
- return manifest._icon;
- }
-
- // Get all sizes orderer largest to smallest
- var icons = manifest.icons;
- if (!icons) {
- return 'style/images/default.png';
- }
-
- // If the icons is not fully-qualifed URL, add the origin of the
- // application to it (technically, manifests are supposed to
- // have those). Otherwise return the url directly as it could be
- // a data: url.
- var PREFERRED_ICON_SIZE = 64;
- var icon = icons[getPreferredSize(icons, PREFERRED_ICON_SIZE)];
- if (icon &&
- (icon.indexOf('data:') !== 0) &&
- (icon.indexOf('http://') !== 0) &&
- (icon.indexOf('https://') !== 0)) {
- icon = origin + icon;
- }
-
- return manifest._icon = icon;
- }
-
- /*
- * Localize the app name
- *
- * {String} App origin
- *
- */
- function getName(origin) {
- var manifest = getManifest(origin);
- if (!manifest) {
- return null;
- }
-
- if ('locales' in manifest) {
- var locale = manifest.locales[document.documentElement.lang];
- if (locale && locale.name) {
- return locale.name;
- }
- }
-
- return manifest.name;
- }
-
- function launch(origin, params) {
- var app = getByOrigin(origin);
- if (!app) {
- return;
- }
-
- app.launch(params);
- }
-
- function isReady() {
- return ready;
- }
-
- function installBookmark(bookmark) {
- if (installedApps[bookmark.origin]) {
- return;
- }
- installedApps[bookmark.origin] = bookmark;
-
- var icon = getIcon(bookmark.origin);
- // No need to put data: URIs in the cache
- if (icon && icon.indexOf('data:') == -1) {
- try {
- window.applicationCache.mozAdd(icon);
- } catch (e) {}
- }
-
- callbacks.forEach(function(callback) {
- if (callback.type == 'install') {
- callback.callback(bookmark);
- }
- });
- }
-
- function addBookmark(bookmark) {
- if (!installedApps[bookmark.origin]) {
- installedApps[bookmark.origin] = bookmark;
- }
- }
-
- function deleteBookmark(bookmark) {
- if (installedApps[bookmark.origin]) {
- delete installedApps[bookmark.origin];
- }
- }
-
- function isInstalled(origin) {
- return installedApps[origin];
- }
-
- return {
- launch: launch,
- isCore: isCore,
- addEventListener: addEventListener,
- getAll: getAll,
- getByOrigin: getByOrigin,
- getOrigin: getOrigin,
- getName: getName,
- getIcon: getIcon,
- cacheIcon: cacheIcon,
- getManifest: getManifest,
- getInstalledApplications: getInstalledApplications,
- isReady: isReady,
- addBookmark: addBookmark,
- deleteBookmark: deleteBookmark,
- installBookmark: installBookmark,
- isInstalled: isInstalled
- };
-})();
View
56 apps/homescreen/js/bookmark.js
@@ -2,10 +2,10 @@
'use strict';
var Bookmark = function Bookmark(params) {
- this.origin = params.url;
-
this.removable = true;
+
this.isBookmark = true;
+ this.url = this.bookmarkURL = this.origin = params.bookmarkURL;
this.manifest = {
name: params.name,
@@ -17,52 +17,19 @@ var Bookmark = function Bookmark(params) {
};
Bookmark.prototype = {
- launch: function bookmark_launch(url, name) {
+ launch: function bookmark_launch() {
var features = {
name: this.manifest.name.replace(/\s/g, '&nbsp;'),
icon: this.manifest.icons['60']
};
- if (!Applications.isInstalled(this.origin)) {
- features.origin = {
- name: features.name,
- url: encodeURIComponent(this.origin)
- };
- }
-
- if (url && url !== this.origin && !Applications.isInstalled(url)) {
- var searchName = navigator.mozL10n.get('wrapper-search-name', {
- topic: name,
- name: this.manifest.name
- }).replace(/\s/g, '&nbsp;');
-
- features.name = searchName;
- features.search = {
- name: searchName,
- url: encodeURIComponent(url)
- };
- }
-
// The third parameter is received in window_manager without whitespaces
// so we decice replace them for &nbsp;
- return window.open(url || this.origin, '_blank', JSON.stringify(features));
+ return window.open(this.url, '_blank', JSON.stringify(features));
},
uninstall: function bookmark_uninstall() {
- var self = this;
- HomeState.deleteBookmark(this.origin,
- function() {
- if (DockManager.contains(self)) {
- DockManager.uninstall(self);
- } else {
- GridManager.uninstall(self);
- }
- Applications.deleteBookmark(self);
- },
- function(er) {
- console.error('Error deleting bookmark ' + er);
- }
- );
+ GridManager.uninstall(this);
}
};
@@ -90,16 +57,9 @@ var BookmarkEditor = {
save: function bookmarkEditor_save() {
this.data.name = this.bookmarkTitle.value;
- this.data.url = this.bookmarkUrl.value;
- var data = this.data;
- HomeState.saveBookmark(data,
- function home_okInstallBookmark() {
- Applications.installBookmark(new Bookmark(data));
- },
- function home_errorInstallBookmark(code) {
- console.error('Error saving bookmark ' + code);
- }
- );
+ this.data.bookmarkURL = this.bookmarkUrl.value;
+ var app = new Bookmark(this.data);
+ GridManager.install(app);
this.close();
}
};
View
115 apps/homescreen/js/dock.js
@@ -5,8 +5,8 @@ const DockManager = (function() {
var container, dock;
- var maxNumAppInDock = 7, maxNumAppInViewPort, numAppsBeforeDrag,
- maxOffsetLeft;
+ var MAX_NUM_ICONS = 7;
+ var maxNumAppInViewPort, numAppsBeforeDrag, maxOffsetLeft;
var windowWidth = window.innerWidth;
var duration = .2;
@@ -21,7 +21,7 @@ const DockManager = (function() {
evt.stopPropagation();
initialOffsetLeft = dock.getLeft();
initialOffsetRight = dock.getRight();
- numApps = dock.getNumApps();
+ numApps = dock.getNumIcons();
startX = evt.clientX;
attachEvents();
break;
@@ -84,7 +84,7 @@ const DockManager = (function() {
if (GridManager.pageHelper.getCurrentPageNumber() > 1) {
Homescreen.setMode('edit');
- if ('origin' in evt.target.dataset) {
+ if ('isIcon' in evt.target.dataset) {
DragDropManager.start(evt, {x: evt.clientX, y: evt.clientY});
}
}
@@ -109,7 +109,7 @@ const DockManager = (function() {
}
function onTouchEnd(scrollX) {
- if (dock.getNumApps() <= maxNumAppInViewPort ||
+ if (dock.getNumIcons() <= maxNumAppInViewPort ||
dock.getLeft() === 0 || dock.getRight() === windowWidth) {
// No animation
delete document.body.dataset.transitioning;
@@ -123,15 +123,6 @@ const DockManager = (function() {
});
}
- /*
- * UI Localization
- *
- * Currently we only translate the app names
- */
- function localize() {
- dock.translate();
- }
-
function releaseEvents() {
container.removeEventListener('contextmenu', handleEvent);
window.removeEventListener('mousemove', handleEvent);
@@ -144,17 +135,6 @@ const DockManager = (function() {
window.addEventListener('mouseup', handleEvent);
}
- function initialize(elem) {
- container = elem;
- container.addEventListener('mousedown', handleEvent);
- }
-
- function render(apps) {
- dock = new Dock();
- dock.render(apps, container);
- localize();
- }
-
function placeAfterRemovingApp(numApps, centering) {
document.body.dataset.transitioning = 'true';
@@ -174,37 +154,28 @@ const DockManager = (function() {
/*
* Initializes the dock
*
- * @param {DOMElement} container
- */
- init: function dm_init(elem) {
- initialize(elem);
- this.getShortcuts(function dm_getShortcuts(apps) {
- render(apps);
- var numApps = dock.getNumApps();
- cellWidth = dock.getWidth() / numApps;
- maxNumAppInViewPort = Math.floor(windowWidth / cellWidth);
- maxOffsetLeft = windowWidth - numApps * cellWidth;
- if (numApps <= maxNumAppInViewPort) {
- dock.moveBy(maxOffsetLeft / 2);
- }
- });
- },
-
- /*
- * Returns list of shortcuts
+ * @param {DOMElement} containerEl
+ * The HTML element that contains the dock.
*
- * @param {Object} the callback
+ * @param {Dock} page
+ * The dock page object.
*/
- getShortcuts: function dm_getShortcuts(callback) {
- HomeState.getShortcuts(callback,
- function gs_fail() {
- callback([]);
- }
- );
+ init: function dm_init(containerEl, page) {
+ container = containerEl;
+ container.addEventListener('mousedown', handleEvent);
+ dock = this.page = page;
+
+ var numIcons= dock.getNumIcons();
+ cellWidth = dock.getWidth() / numIcons;
+ maxNumAppInViewPort = Math.floor(windowWidth / cellWidth);
+ maxOffsetLeft = windowWidth - numIcons * cellWidth;
+ if (numIcons <= maxNumAppInViewPort) {
+ dock.moveBy(maxOffsetLeft / 2);
+ }
},
onDragStop: function dm_onDragStop() {
- var numApps = dock.getNumApps();
+ var numApps = dock.getNumIcons();
maxOffsetLeft = windowWidth - numApps * cellWidth;
if (numApps === numAppsBeforeDrag ||
numApps > maxNumAppInViewPort &&
@@ -219,56 +190,28 @@ const DockManager = (function() {
onDragStart: function dm_onDragStart() {
releaseEvents();
- numAppsBeforeDrag = dock.getNumApps();
+ numAppsBeforeDrag = dock.getNumIcons();
},
/*
* Exports the page
*/
- get page() {
- return dock;
- },
-
- contains: function dm_contains(app) {
- return dock.getIcon(Applications.getOrigin(app));
- },
+ page: null,
/*
- * Removes an application from the dock
- *
- * {Object} moz app
+ * Update display after removing an app.
*/
- uninstall: function dm_uninstall(app) {
- if (!this.contains(app)) {
- return;
- }
-
- dock.remove(app);
- this.saveState();
-
- maxOffsetLeft = windowWidth - dock.getNumApps() * cellWidth;
- var numApps = dock.getNumApps();
+ afterRemovingApp: function dm_afterRemovingApp() {
+ maxOffsetLeft = windowWidth - dock.getNumIcons() * cellWidth;
+ var numApps = dock.getNumIcons();
if (numApps > maxNumAppInViewPort && dock.getRight() >= windowWidth) {
return;
}
-
placeAfterRemovingApp(numApps);
},
isFull: function dm_isFull() {
- return dock.getNumApps() === maxNumAppInDock;
- },
-
- localize: localize,
-
- /*
- * Save current state
- *
- * {String} the mode ('edit' or 'mode')
- */
- saveState: function dm_saveState() {
- var nop = function f_nop() {};
- HomeState.saveShortcuts(dock.getAppsList(), nop, nop);
+ return dock.getNumIcons() === MAX_NUM_ICONS;
},
goNextSet: goNextSet,
View
38 apps/homescreen/js/dragdrop.js
@@ -18,7 +18,8 @@ const DragDropManager = (function() {
*/
var disabledCheckingLimitsTimeout = null;
- var draggableIcon, draggableIconOrigin, previousOverlapIcon,
+ var draggableIcon,
+ previousOverlapIcon,
overlapingTimeout;
var pageHelper = GridManager.pageHelper;
@@ -54,8 +55,8 @@ const DragDropManager = (function() {
if (!overlapingDock && !DockManager.isFull()) {
// I've just entered
draggableIcon.addClassToDragElement('overDock');
- pageHelper.getCurrent().remove(draggableIcon);
- DockManager.page.append(draggableIcon);
+ var needsRender = false;
+ DockManager.page.appendIcon(draggableIcon, needsRender);
drop(overlapElem, DockManager.page);
previousOverlapIcon = overlapElem;
}
@@ -75,10 +76,10 @@ const DragDropManager = (function() {
if (overlapingDock) {
draggableIcon.removeClassToDragElement('overDock');
overlapingDock = false;
- DockManager.page.remove(draggableIcon);
var curPageObj = pageHelper.getCurrent();
- if (curPageObj.getNumApps() < pageHelper.getMaxPerPage()) {
- curPageObj.append(draggableIcon);
+ if (curPageObj.getNumIcons() < pageHelper.maxIconsPerPage) {
+ var needsRender = false;
+ curPageObj.appendIcon(draggableIcon, needsRender);
} else {
curPageObj.insertBeforeLastIcon(draggableIcon);
}
@@ -91,15 +92,13 @@ const DragDropManager = (function() {
var curPageObj = pageHelper.getCurrent();
if (pageHelper.getCurrentPageNumber() <
pageHelper.getTotalPagesNumber() - 1) {
- curPageObj.remove(draggableIcon);
pageHelper.getNext().prependIcon(draggableIcon);
setDisabledCheckingLimits(true);
transitioning = true;
GridManager.goToNextPage(onNavigationEnd);
- } else if (curPageObj.getNumApps() > 1) {
+ } else if (curPageObj.getNumIcons() > 1) {
// New page if there are two or more icons
- curPageObj.remove(draggableIcon);
- pageHelper.push([draggableIcon]);
+ pageHelper.addPage([draggableIcon]);
setDisabledCheckingLimits(true);
transitioning = true;
GridManager.goToNextPage(onNavigationEnd);
@@ -111,12 +110,12 @@ const DragDropManager = (function() {
}
var curPageObj = pageHelper.getCurrent();
- curPageObj.remove(draggableIcon);
var prevPageObj = pageHelper.getPrevious();
- if (prevPageObj.getNumApps() === pageHelper.getMaxPerPage()) {
+ if (prevPageObj.getNumIcons() === pageHelper.maxIconsPerPage) {
prevPageObj.insertBeforeLastIcon(draggableIcon);
} else {
- prevPageObj.append(draggableIcon);
+ var needsRender = false;
+ prevPageObj.appendIcon(draggableIcon, needsRender);
}
setDisabledCheckingLimits(true);
transitioning = true;
@@ -149,8 +148,7 @@ const DragDropManager = (function() {
* {Object} This is the DOMElement which was tapped and hold
*/
function onStart(elem) {
- draggableIconOrigin = elem.dataset.origin;
- draggableIcon = getPage().getIcon(draggableIconOrigin);
+ draggableIcon = GridManager.getIcon(elem.dataset);
draggableIcon.onDragStart(startEvent.x, startEvent.y);
if (overlapingDock) {
draggableIcon.addClassToDragElement('overDock');
@@ -183,18 +181,18 @@ const DragDropManager = (function() {
function drop(overlapElem, page) {
var classList = overlapElem.classList;
if (classList.contains('icon') || classList.contains('options')) {
- var overlapElemOrigin = overlapElem.dataset.origin;
- page.drop(draggableIconOrigin, overlapElemOrigin);
+ var overlapIcon = GridManager.getIcon(overlapElem.dataset);
+ page.drop(draggableIcon, overlapIcon);
} else if (classList.contains('dockWrapper')) {
var firstIcon = page.getFirstIcon();
if (currentEvent.x < firstIcon.getLeft()) {
if (draggableIcon !== firstIcon) {
- page.drop(draggableIconOrigin, firstIcon.getOrigin());
+ page.drop(draggableIcon, firstIcon);
}
} else {
var lastIcon = page.getLastIcon();
if (draggableIcon !== lastIcon) {
- page.drop(draggableIconOrigin, lastIcon.getOrigin());
+ page.drop(draggableIcon, lastIcon);
}
}
}
@@ -230,7 +228,7 @@ const DragDropManager = (function() {
if (classList.contains('page')) {
var lastIcon = page.getLastIcon();
if (currentEvent.y > lastIcon.getTop() && draggableIcon !== lastIcon) {
- page.drop(draggableIconOrigin, lastIcon.getOrigin());
+ page.drop(draggableIcon, lastIcon);
}
} else {
overlapingTimeout = setTimeout(drop, 500, overlapElem, page);
View
717 apps/homescreen/js/grid.js
@@ -1,8 +1,10 @@
-
-
'use strict';
const GridManager = (function() {
+ var MAX_ICONS_PER_PAGE = 4 * 4;
+ var PREFERRED_ICON_SIZE = 64;
+ var SAVE_STATE_TIMEOUT = 100;
+
var container;
var windowWidth = window.innerWidth;
@@ -13,13 +15,17 @@ const GridManager = (function() {
var opacityOnAppGridPageMax = .7;
var kPageTransitionDuration = .3;
- var overlay = document.querySelector('#landing-overlay');
- var overlayStyle = overlay.style;
+ var overlay, overlayStyle;
var overlayTransition = 'opacity ' + kPageTransitionDuration + 's ease';
+ var numberOfSpecialPages = 0;
var pages = [];
var currentPage = 1;
+ var saveStateTimeout = null;
+
+ var appMgr = navigator.mozApps.mgmt;
+
// Limits for changing pages during dragging
var limits = {
left: 0,
@@ -164,7 +170,7 @@ const GridManager = (function() {
break;
case 'contextmenu':
- if (currentPage > 1 && 'origin' in evt.target.dataset) {
+ if (currentPage > 1 && 'isIcon' in evt.target.dataset) {
evt.stopImmediatePropagation();
Homescreen.setMode('edit');
DragDropManager.start(evt, {
@@ -195,7 +201,7 @@ const GridManager = (function() {
var page = currentPage;
if (Math.abs(deltaX) > thresholdForPanning) {
var forward = dirCtrl.goesForward(deltaX);
- if (forward && currentPage < pageHelper.total() - 1) {
+ if (forward && currentPage < pages.length - 1) {
page = page + 1;
} else if (!forward &&
(page === 1 || page >= 3 ||
@@ -300,194 +306,64 @@ const GridManager = (function() {
}
/*
- * Renders the homescreen from moz applications
- */
- function renderFromMozApps(finish) {
- var apps = Applications.getAll();
-
- var xhr = new XMLHttpRequest();
- xhr.overrideMimeType('application/json');
- xhr.open('GET', 'js/init.json', true);
- xhr.send(null);
-
- xhr.onreadystatechange = function renderFromMozApps_init(evt) {
- if (xhr.readyState != 4)
- return;
-
- if (xhr.status == 0 || xhr.status == 200) {
- try {
- var init = JSON.parse(xhr.responseText);
- init.grid.forEach(function(page) {
- pageHelper.push(page);
-
- for (var i = apps.length - 1; i >= 0; i--) {
- if (page.indexOf(apps[i]['origin']) != -1) {
- apps.splice(i, 1);
- }
- }
- });
-
- for (var i = apps.length - 1; i >= 0; i--) {
- if (init.dock.indexOf(apps[i]['origin']) != -1) {
- apps.splice(i, 1);
- }
- }
- HomeState.saveShortcuts(init.dock);
-
- for (var i = apps.length - 1; i >= 0; i--) {
- if (init.hidden.indexOf(apps[i]['origin']) != -1) {
- apps.splice(i, 1);
- }
- }
- HomeState.saveHiddens(init.hidden);
-
- } catch (e) {
- dump('Failed parsing homescreen configuration file: ' + e + '\n');
- }
-
- var max = pageHelper.getMaxPerPage();
- var list = [];
- for (var i = 0; i < apps.length; i++) {
- list.push(apps[i]);
- if (list.length === max) {
- pageHelper.push(list);
- list = [];
- }
- }
-
- if (list.length > 0) {
- pageHelper.push(list);
- }
-
- // Renders pagination bar
- finish();
-
- // Saving initial state
- pageHelper.saveAll();
- }
- }
- }
-
- /*
- * Renders the homescreen from the database
- */
- function renderFromDB(finish) {
- var appsInDB = [];
- HomeState.getAppsByPage(
- function iterate(apps) {
- appsInDB = appsInDB.concat(apps);
-
- for (var app in apps) {
- Applications.cacheIcon(apps[app].origin, apps[app].icon);
- }
- pageHelper.push(apps.map(function(app) { return app.origin; }));
- },
- function onsuccess(results) {
- if (results === 0) {
- renderFromMozApps(finish);
- return;
- }
-
- var installedApps = Applications.getInstalledApplications();
- var len = appsInDB.length;
- for (var i = 0; i < len; i++) {
- var origin = appsInDB[i].origin;
- if (origin in installedApps) {
- delete installedApps[origin];
- }
- }
-
- DockManager.getShortcuts(function getShortcuts(shortcuts) {
- var len = shortcuts.length;
- for (var i = 0; i < len; i++) {
- var origin = shortcuts[i].origin || shortcuts[i];
- if (origin in installedApps) {
- Applications.cacheIcon(origin, shortcuts[i].icon);
- delete installedApps[origin];
- }
- }
-
- HomeState.getHiddens(function(hidden) {
-
- if (hidden) {
- var len = hidden.length;
- for (var i = 0; i < len; i++) {
- var origin = hidden[i].origin || hidden[i];
- if (origin in installedApps) {
- delete installedApps[origin];
- }
- }
- }
-
- for (var origin in installedApps) {
- GridManager.install(installedApps[origin]);
- }
-
- updatePaginationBar();
- finish();
- });
- });
- },
- function onerror() {
- // Error recovering info about apps
- renderFromMozApps(finish);
- }
- );
- }
-
- /*
* UI Localization
*
*/
var dirCtrl = {};
- function getDirCtrl() {
+ function setDirCtrl() {
function goesLeft(x) { return (x > 0); }
function goesRight(x) { return (x < 0); }
function limitLeft(x) { return (x < limits.left); }
function limitRight(x) { return (x > limits.right); }
var rtl = (document.documentElement.dir == 'rtl');
- return {
- offsetPrev: rtl ? -1 : 1,
- offsetNext: rtl ? 1 : -1,
- limitPrev: rtl ? limitRight : limitLeft,
- limitNext: rtl ? limitLeft : limitRight,
- translatePrev: rtl ? 'translateX(100%)' : 'translateX(-100%)',
- translateNext: rtl ? 'translateX(-100%)' : 'translateX(100%)',
- goesForward: rtl ? goesLeft : goesRight
- };
+
+ dirCtrl.offsetPrev = rtl ? -1 : 1;
+ dirCtrl.offsetNext = rtl ? 1 : -1;
+ dirCtrl.limitPrev = rtl ? limitRight : limitLeft;
+ dirCtrl.limitNext = rtl ? limitLeft : limitRight;
+ dirCtrl.translatePrev = rtl ? 'translateX(100%)' : 'translateX(-100%)';
+ dirCtrl.translateNext = rtl ? 'translateX(-100%)' : 'translateX(100%)';
+ dirCtrl.goesForward = rtl ? goesLeft : goesRight;
}
+ var haveLocale = false;
+
function localize() {
// switch RTL-sensitive methods accordingly
- dirCtrl = getDirCtrl();
+ setDirCtrl();
- pages.forEach(function translate(page) {
- page.translate();
- });
+ for each (var iconsForApp in appIcons) {
+ for each (var icon in iconsForApp) {
+ icon.translate();
+ }
+ }
+ for each (var icon in bookmarkIcons) {
+ icon.translate();
+ }
+
+ haveLocale = true;
}
function getFirstPageWithEmptySpace() {
- var maxPerPage = pageHelper.getMaxPerPage();
-
- var pagesCount = pageHelper.total();
- for (var i = 2; i < pagesCount; i++) {
- if (pages[i].getNumApps() < maxPerPage) {
+ for (var i = numberOfSpecialPages; i < pages.length; i++) {
+ if (pages[i].getNumIcons() < MAX_ICONS_PER_PAGE) {
return i;
}
}
-
- return pagesCount;
+ return pages.length;
}
function removeEmptyPages() {
pages.forEach(function checkIsEmpty(page, index) {
// ignore the landing page
- if (index <= 1) {
+ if (index < numberOfSpecialPages) {
return;
}
- if (page.getNumApps() === 0) {
+ if (page.getNumIcons() === 0) {
pageHelper.remove(index);
+ if (currentPage >= index)
+ currentPage -= 1;
}
});
}
@@ -499,45 +375,41 @@ const GridManager = (function() {
* pages with a number of apps greater that the maximum
*/
function ensurePagesOverflow() {
- var max = pageHelper.getMaxPerPage();
-
pages.forEach(function checkIsOverflow(page, index) {
// ignore the landing page
- if (index <= 1) {
+ if (index < numberOfSpecialPages) {
return;
}
// if the page is not full
- if (page.getNumApps() <= max) {
- return;
- }
-
- var propagateIco = page.popIcon();
- if (index === pageHelper.total() - 1) {
- pageHelper.push([propagateIco]); // new page
- } else {
- pages[index + 1].prependIcon(propagateIco); // next page
+ while (page.getNumIcons() > MAX_ICONS_PER_PAGE) {
+ var propagateIco = page.popIcon();
+ if (index === pages.length - 1) {
+ pageHelper.addPage([propagateIco]); // new page
+ } else {
+ pages[index + 1].prependIcon(propagateIco); // next page
+ }
}
});
}
var pageHelper = {
+
+ maxIconsPerPage: MAX_ICONS_PER_PAGE,
+
/*
* Adds a new page to the grid layout
*
- * @param {Array} initial list of apps or icons
+ * @param {Array} icons
+ * List of Icon objects.
*/
- push: function(apps, appsFromMarket) {
- var index = this.total();
- var page = new Page(index);
+ addPage: function(icons) {
+ var pageElement = document.createElement('div');
+ var page = new Page(pageElement, icons);
pages.push(page);
- var pageElement = document.createElement('div');
pageElement.className = 'page';
container.appendChild(pageElement);
-
- page.render(apps, pageElement);
-
updatePaginationBar();
},
@@ -547,43 +419,22 @@ const GridManager = (function() {
* @param {int} index of the page
*/
remove: function gm_remove(index) {
- goToPage(index - 1);
-
pages[index].destroy(); // Destroy page
pages.splice(index, 1); // Removes page from the list
updatePaginationBar();
},
/*
- * Returns the total number of pages
- */
- total: function() {
- return pages.length;
- },
-
- /*
- * Saves the page state on the database
- */
- save: function(index) {
- HomeState.saveGrid({
- id: index,
- apps: pages[index].getAppsList()
- });
- },
-
- /*
* Saves all pages state on the database
*/
saveAll: function() {
- HomeState.saveGrid(pages.slice(2));
- },
-
- /*
- * Returns the total number of apps for each page. It could be
- * more clever. Currently there're sixteen apps for page
- */
- getMaxPerPage: function() {
- return 4 * 4;
+ var state = pages.slice(numberOfSpecialPages);
+ state.unshift(DockManager.page);
+ for (var i = 0; i < state.length; i++) {
+ var page = state[i];
+ state[i] = {index: i, icons: page.getIconDescriptors()};
+ }
+ HomeState.saveGrid(state);
},
getNext: function() {
@@ -599,41 +450,351 @@ const GridManager = (function() {
},
getLast: function() {
- return pages[this.total() - 1];
+ return pages[pages.length - 1];
},
getCurrentPageNumber: function() {
return currentPage;
},
+ /*
+ * Returns the total number of pages
+ */
getTotalPagesNumber: function() {
- return pageHelper.total();
+ return pages.length;
}
};
+
+ /*
+ * Look up Icon objects using a descriptor containing 'manifestURL'
+ * (optionally 'entry_point') or 'bookmarkURL'.
+ */
+
+ // Map 'bookmarkURL' -> Icon object.
+ var bookmarkIcons = Object.create(null);
+ // Map 'manifestURL' + 'entry_point' to Icon object.
+ var appIcons = Object.create(null);
+ // Map 'origin' -> app object.
+ var appsByOrigin = Object.create(null);
+
+ function rememberIcon(icon) {
+ var descriptor = icon.descriptor;
+ if (descriptor.bookmarkURL) {
+ bookmarkIcons[descriptor.bookmarkURL] = icon;
+ return;
+ }
+ var iconsForApp = appIcons[descriptor.manifestURL];
+ if (!iconsForApp)
+ iconsForApp = appIcons[descriptor.manifestURL] = Object.create(null);
+
+ iconsForApp[descriptor.entry_point || ""] = icon;
+ }
+
+ function forgetIcon(icon) {
+ var descriptor = icon.descriptor;
+ if (descriptor.bookmarkURL) {
+ delete bookmarkIcons[descriptor.bookmarkURL];
+ return;
+ }
+ var iconsForApp = appIcons[descriptor.manifestURL];
+ if (!iconsForApp)
+ return;
+
+ delete iconsForApp[descriptor.entry_point || ""];
+ }
+
+ function getIcon(descriptor) {
+ if (descriptor.bookmarkURL)
+ return bookmarkIcons[descriptor.bookmarkURL];
+
+ var iconsForApp = appIcons[descriptor.manifestURL];
+ return iconsForApp && iconsForApp[descriptor.entry_point || ""];
+ }
+
+ function getIconsForApp(app) {
+ return appIcons[descriptor.manifestURL];
+ }
+
+ function getIconForBookmark(bookmarkURL) {
+ return bookmarkIcons[bookmarkURL];
+ }
+
+ // Ways to enumerate installed apps & bookmarks and find out whether
+ // a certain "origin" is available as an existing installed app or
+ // bookmark. Only used by Everything.me at this point.
+ function getApps() {
+ var apps = [];
+ for (var origin in appsByOrigin) {
+ apps.push(appsByOrigin[origin]);
+ }
+ return apps;
+ }
+
+ function getAppByOrigin(url) {
+ return appsByOrigin[url];
+ }
+
+
+ /*
+ * Initialize the UI.
+ */
+ function initUI(selector) {
+ overlay = document.querySelector('#landing-overlay');
+ overlayStyle = overlay.style;
+
+ container = document.querySelector(selector);
+ container.addEventListener('contextmenu', handleEvent);
+ container.addEventListener('mousedown', handleEvent, true);
+
+ limits.left = container.offsetWidth * 0.05;
+ limits.right = container.offsetWidth * 0.95;
+
+ setDirCtrl();
+
+ // Create stub Page objects for the special pages that are
+ // not backed by the app database. Note that this creates an
+ // offset between these indexes here and the ones in the DB.
+ // See also pageHelper.saveAll().
+ numberOfSpecialPages = container.children.length;
+ for (var i = 0; i < container.children.length; i++) {
+ var pageElement = container.children[i];
+ var page = new Page(pageElement, null);
+ pages.push(page);
+ }
+ }
+
+ /*
+ * Initialize the mozApps event handlers and synchronize our grid
+ * state with the applications known to the system.
+ */
+ function initApps(apps) {
+ appMgr.oninstall = function oninstall(event) {
+ GridManager.install(event.application);
+ };
+ appMgr.onuninstall = function onuninstall(event) {
+ GridManager.uninstall(event.application);
+ };
+
+ appMgr.getAll().onsuccess = function onsuccess(event) {
+ // Create a copy of all icons we know about so we can find out which icons
+ // should be removed.
+ var iconsByManifestURL = Object.create(null);
+ for (var manifestURL in appIcons) {
+ iconsByManifestURL[manifestURL] = appIcons[manifestURL];
+ }
+
+ // Add an empty page where we drop the icons for any extra apps we discover
+ // at this stage.
+ pageHelper.addPage([]);
+
+ var apps = event.target.result;
+ apps.forEach(function eachApp(app) {
+ delete iconsByManifestURL[app.manifestURL];
+ processApp(app);
+ });
+
+ for (var manifestURL in iconsByManifestURL) {
+ var iconsForApp = iconsByManifestURL[manifestURL];
+ for (var entryPoint in iconsForApp) {
+ var icon = iconsForApp[entryPoint];
+ icon.remove();
+ GridManager.markDirtyState();
+ }
+ }
+
+ ensurePagesOverflow();
+ removeEmptyPages();
+ };
+ }
+
+ /*
+ * Create Icon objects from the descriptors we save in IndexedDB.
+ */
+ function convertDescriptorsToIcons(pageState) {
+ var icons = pageState.icons;
+ for (var i = 0; i < icons.length; i++) {
+ var descriptor = icons[i];
+ // navigator.mozApps backed app will objects will be handled
+ // asynchronously and therefore at a later time.
+ var app = null;
+ if (descriptor.bookmarkURL)
+ app = new Bookmark(descriptor);
+
+ var icon = icons[i] = new Icon(descriptor, app);
+ rememberIcon(icon);
+ }
+ return icons;
+ }
+
+ /*
+ * Process an Application object as retrieved from the
+ * navigator.mozApps.mgmt API (or a Bookmark object) and create
+ * corresponding icon(s) for it (an app can have multiple entry
+ * points, each one is represented as an icon.)
+ */
+ function processApp(app, withAnimation, callback) {
+ // Ignore system apps.
+ if (HIDDEN_APPS.indexOf(app.manifestURL) != -1)
+ return;
+
+ appsByOrigin[app.origin] = app;
+
+ var manifest = app.manifest;
+ if (!manifest)
+ return;
+
+ var entryPoints = manifest.entry_points;
+ if (!entryPoints) {
+ createOrUpdateIconForApp(app, withAnimation);
+ return;
+ }
+
+ for (var entryPoint in entryPoints) {
+ if (!entryPoints[entryPoint].icons)
+ continue;
+
+ createOrUpdateIconForApp(app, withAnimation, entryPoint);
+ }
+ }
+
+ /*
+ * Create or update a single icon for an Application (or Bookmark) object.
+ */
+ function createOrUpdateIconForApp(app, withAnimation, entryPoint) {
+ // Make sure we update the icon/label when the app is updated.
+ if (!app.isBookmark) {
+ app.ondownloadapplied = function ondownloadapplied(event) {
+ var withAnimation = false;
+ createOrUpdateIconForApp(app, withAnimation, entryPoint);
+ };
+ }
+
+ var manifest = app.manifest;
+ var iconsAndNameHolder = manifest;
+ if (entryPoint)
+ iconsAndNameHolder = manifest.entry_points[entryPoint];
+
+ var descriptor = {
+ bookmarkURL: app.bookmarkURL,
+ manifestURL: app.manifestURL,
+ entry_point: entryPoint,
+ removable: app.removable,
+ name: iconsAndNameHolder.name,
+ icon: bestMatchingIcon(app, iconsAndNameHolder)
+ };
+ if (haveLocale && !app.isBookmark) {
+ var locales = iconsAndNameHolder.locales;
+ if (locales) {
+ var locale = locales[document.documentElement.lang];
+ if (locale && locale.name)
+ descriptor.localizedName = locale.name;
+ }
+ }
+
+ // If there's an existing icon for this bookmark/app/entry point already, let
+ // it update itself.
+ var existingIcon = getIcon(descriptor);
+ if (existingIcon) {
+ existingIcon.update(descriptor, app);
+ return;
+ }
+
+ if (withAnimation)
+ descriptor.hidden = true;
+
+ var icon = new Icon(descriptor, app);
+ rememberIcon(icon);
+
+ // Normally we just silently add icons to the last page, unless we're
+ // installing an app/bookmark with a visibile animation. Then we want
+ // to pick the first page with an empty space.
+ var index = pages.length - 1;
+ if (withAnimation)
+ index = getFirstPageWithEmptySpace();
+
+ if (index < pages.length) {
+ pages[index].appendIcon(icon);
+ } else {
+ pageHelper.addPage([icon]);
+ }
+
+ GridManager.markDirtyState();
+
+ if (withAnimation) {
+ goToPage(index, function install_goToPage() {
+ icon.show();
+ });
+ }
+ }
+
+ function bestMatchingIcon(app, manifest) {
+ var icons = manifest.icons;
+ if (!icons)
+ return Icon.prototype.DEFAULT_ICON_URL;
+
+ var preferredSize = Number.MAX_VALUE;
+ var max = 0;
+
+ for (var size in icons) {
+ size = parseInt(size, 10);
+ if (size > max)
+ max = size;
+
+ if (size >= PREFERRED_ICON_SIZE && size < preferredSize)
+ preferredSize = size;
+ }
+ // If there is an icon matching the preferred size, we return the result,
+ // if there isn't, we will return the maximum available size.
+ if (preferredSize === Number.MAX_VALUE)
+ preferredSize = max;
+
+ var url = icons[preferredSize];
+ if (!url)
+ return Icon.prototype.DEFAULT_ICON_URL;
+
+ // If the icon path is not an absolute URL, prepend the app's origin.
+ if (url.indexOf('data:') == 0 ||
+ url.indexOf('app://') == 0 ||
+ url.indexOf('http://') == 0 ||
+ url.indexOf('https://') == 0)
+ return url;
+
+ return app.origin + url;
+ }
+
+
return {
/*
* Initializes the grid manager
*
- * @param {String} selector of the container for applications
+ * @param {String} selector
+ * Specifies the HTML container element for the pages.
*
*/
- init: function gm_init(selector, finish) {
- container = document.querySelector(selector);
- for (var i = 0; i < container.children.length; i++) {
- var page = new Page(i);
- page.render([], container.children[i]);
- pages.push(page);
- }
-
- container.addEventListener('contextmenu', handleEvent);
- container.addEventListener('mousedown', handleEvent, true);
-
- limits.left = container.offsetWidth * 0.05;
- limits.right = container.offsetWidth * 0.95;
-
- dirCtrl = getDirCtrl();
- renderFromDB(finish);
+ init: function gm_init(gridSelector, dockSelector, callback) {
+ initUI(gridSelector);
+
+ // Initialize the grid from the state saved in IndexedDB.
+ HomeState.init(function eachPage(pageState) {
+ // First 'page' is the dock.
+ if (pageState.index == 0) {
+ var dockContainer = document.querySelector(dockSelector);
+ var dock = new Dock(dockContainer, convertDescriptorsToIcons(pageState));
+ DockManager.init(dockContainer, dock);
+ return;
+ }
+ pageHelper.addPage(convertDescriptorsToIcons(pageState));
+ }, function onState() {
+ initApps();
+ callback();
+ }, function onError(error) {
+ var dockContainer = document.querySelector(dockSelector);
+ var dock = new Dock(dockContainer, []);
+ DockManager.init(dockContainer, dock);
+ initApps();
+ callback();
+ });
},
onDragStart: function gm_onDragSart() {
@@ -653,57 +814,71 @@ const GridManager = (function() {
* Adds a new application to the layout when the user installed it
* from market
*
- * {Object} moz app
+ * @param {Application} app
+ * The application (or bookmark) object
*/
- install: function gm_install(app, animation) {
- var index = getFirstPageWithEmptySpace();
- var origin = Applications.getOrigin(app);
- if (animation) {
- Applications.getManifest(origin).hidden = true;
- }
-
- if (index < pageHelper.total()) {
- pages[index].append(app);
- } else {
- pageHelper.push([app], true);
- }
-
- if (animation) {
- goToPage(index, function install_goToPage() {
- pageHelper.getCurrent().applyInstallingEffect(origin);
- });
- }
-
- pageHelper.saveAll();
+ install: function gm_install(app) {
+ var withAnimation = true;
+ processApp(app, withAnimation);
},
/*
* Removes an application from the layout
*
- * {Object} moz app
+ * @param {Application} app
+ * The application object that's to be uninstalled.
*/
uninstall: function gm_uninstall(app) {
- var index = 0;
- var total = pageHelper.total();
- var origin = Applications.getOrigin(app).toString();
-
- while (index < total) {
- var page = pages[index];
- if (page.getIcon(origin)) {
- page.remove(app);
- break;
+ var updateDock = false;
+ var dock = DockManager.page;
+
+ delete appsByOrigin[app.origin];
+
+ if (app.isBookmark) {
+ var icon = bookmarkIcons[app.bookmarkURL];
+ updateDock = dock.containsIcon(icon);
+ icon.remove();
+ delete bookmarkIcons[app.bookmarkURL];
+ } else {
+ var iconsForApp = appIcons[app.manifestURL];
+ if (!iconsForApp)
+ return;
+
+ for (var entryPoint in iconsForApp) {
+ var icon = iconsForApp[entryPoint];
+ updateDock = updateDock || dock.containsIcon(icon);
+ icon.remove();
}
- index++;
+ delete appIcons[app.manifestURL];
}
+ if (updateDock)
+ DockManager.afterRemovingApp();
+
removeEmptyPages();
- pageHelper.saveAll();
+ this.markDirtyState();
},
- saveState: function gm_saveState() {
- pageHelper.saveAll();
+ markDirtyState: function gm_markDirtyState() {
+ if (saveStateTimeout != null) {
+ window.clearTimeout(saveStateTimeout);
+ }
+ saveStateTimeout = window.setTimeout(function saveStateTrigger() {
+ saveStateTimeout = null;
+ pageHelper.saveAll();
+ }, SAVE_STATE_TIMEOUT);
},
+ getIcon: getIcon,
+
+ getIconsForApp: getIconsForApp,
+
+ getIconForBookmark: getIconForBookmark,
+
+ getApps: getApps,
+
+ getAppByOrigin: getAppByOrigin,
+
goToPage: goToPage,
goToPreviousPage: goToPreviousPage,
@@ -712,12 +887,8 @@ const GridManager = (function() {
localize: localize,
- get dirCtrl() {
- return dirCtrl;
- },
+ dirCtrl: dirCtrl,
- get pageHelper() {
- return pageHelper;
- }
+ pageHelper: pageHelper
};
})();
View
77 apps/homescreen/js/homescreen.js
@@ -3,27 +3,22 @@
const Homescreen = (function() {
var mode = 'normal';
- var _ = navigator.mozL10n.get;
-
- // Initialize the pagination scroller
- PaginationBar.init('.paginationScroller');
- function initUI() {
+ var _ = navigator.mozL10n.get;
+ setLocale();
+ window.addEventListener('localized', function localize() {
setLocale();
- GridManager.init('.apps', function gm_init() {
- DockManager.init(document.querySelector('#footer .dockWrapper'));
- PaginationBar.show();
- GridManager.goToPage(1);
- DragDropManager.init();
- Wallpaper.init();
+ GridManager.localize();
+ });
- window.addEventListener('localized', function localize() {
- setLocale();
- GridManager.localize();
- DockManager.localize();
- });
- });
- }
+ // Initialize the various components.
+ PaginationBar.init('.paginationScroller');
+ GridManager.init('.apps', '.dockWrapper', function gm_init() {
+ PaginationBar.show();
+ GridManager.goToPage(1);
+ DragDropManager.init();
+ Wallpaper.init();
+ });
window.addEventListener('hashchange', function() {
if (document.location.hash != '#root')
@@ -31,8 +26,7 @@ const Homescreen = (function() {
if (Homescreen.isInEditMode()) {
Homescreen.setMode('normal');
- GridManager.saveState();
- DockManager.saveState();
+ GridManager.markDirtyState();
Permissions.hide();
GridManager.goToPage(GridManager.pageHelper.getCurrentPageNumber());
} else {
@@ -46,41 +40,6 @@ const Homescreen = (function() {
document.documentElement.dir = navigator.mozL10n.language.direction;
}
- function start() {
- if (Applications.isReady()) {
- initUI();
- return;
- }
- Applications.addEventListener('ready', initUI);
- }
-
- function loadBookmarks() {
- HomeState.getBookmarks(function(bookmarks) {
- bookmarks.forEach(function(bookmark) {
- Applications.addBookmark(bookmark);
- });
- start();
- }, start);
- }
-
- HomeState.init(function success(onUpgradeNeeded) {
- loadBookmarks();
- }, start);
-
- // Listening for installed apps
- Applications.addEventListener('install', function oninstall(app) {
- GridManager.install(app, true);
- });
-
- // Listening for uninstalled apps
- Applications.addEventListener('uninstall', function onuninstall(app) {
- if (DockManager.contains(app)) {
- DockManager.uninstall(app);
- } else {
- GridManager.uninstall(app);
- }
- });
-
if (navigator.mozSetMessageHandler) {
navigator.mozSetMessageHandler('activity', function onActivity(activity) {
var data = activity.source.data;
@@ -96,12 +55,12 @@ const Homescreen = (function() {
return {
/*
- * Displays the contextual menu given an origin
+ * Displays the contextual menu given an app.
*
- * @param {String} the app origin
+ * @param {Application} app
+ * The application object.
*/
- showAppDialog: function h_showAppDialog(origin) {
- var app = Applications.getByOrigin(origin);
+ showAppDialog: function h_showAppDialog(app) {
var title, body, yesLabel;
// Show a different prompt if the user is trying to remove
// a bookmark shortcut instead of an app.
View
436 apps/homescreen/js/page.js
@@ -1,27 +1,34 @@
-
'use strict';
/*
* Icon constructor
*
- * @param{Object} moz app object
+ * @param {Object} descriptor
+ * An object that contains the data necessary to draw this icon.
+ * @param {Application} app [optional]
+ * The Application or Bookmark object corresponding to this icon.
*/
-var Icon = function Icon(app) {
- var origin = Applications.getOrigin(app);
- this.descriptor = {
- origin: origin,
- name: Applications.getName(origin),
- icon: Applications.getIcon(origin),
- isHidden: Applications.getManifest(origin).hidden,
- isCore: Applications.isCore(app)
- };
-
- this.type = 'ApplicationIcon';
+function Icon(descriptor, app) {
+ this.descriptor = descriptor;
+ this.app = app;
};
Icon.prototype = {
MIN_ICON_SIZE: 52,
MAX_ICON_SIZE: 54,
+
+ DEFAULT_ICON_URL: window.location.protocol + '//' + window.location.host +
+ '/style/images/default.png',
+
+ // These properties will be copied from the descriptor onto the icon's HTML element
+ // dataset and allow us to uniquely look up the Icon object from the HTML element.
+ _descriptorIdentifiers: ['manifestURL', 'entry_point', 'bookmarkURL'],
+
+ /**
+ * The Application (or Bookmark) object corresponding to this icon.
+ */
+ app: null,
+
/*
* Renders the icon into the page
*
@@ -29,9 +36,9 @@ Icon.prototype = {
*
* @param{Object} where the draggable element should be appened
*/
- render: function icon_render(target, page) {
+ render: function icon_render(target) {
/*
- * <li role="button" aria-label="label" class="icon" dataset-origin="zzz">
+ * <li role="button" aria-label="label" class="icon" data-manifestURL="zzz">
* <div>
* <img role="presentation" src="the icon image path"></img>
* <span class="label">label</span>
@@ -41,40 +48,39 @@ Icon.prototype = {
*/
var container = this.container = document.createElement('li');
container.className = 'icon';
- if (this.descriptor.isHidden) {
+ if (this.descriptor.hidden) {
+ delete this.descriptor.hidden;
container.dataset.visible = false;
}
- container.dataset.origin = this.descriptor.origin;
+ var descriptor = this.descriptor;
+ container.dataset.isIcon = true;
+ this._descriptorIdentifiers.forEach(function (prop) {
+ var value = descriptor[prop];
+ if (value)
+ container.dataset[prop] = value;
+ });
+
+ var localizedName = descriptor.localizedName || descriptor.name;
container.setAttribute('role', 'button');
- container.setAttribute('aria-label', this.descriptor.name);
+ container.setAttribute('aria-label', localizedName);
// Icon container
var icon = this.icon = document.createElement('div');
// Image
- var canvas = document.createElement('canvas');
- canvas.setAttribute('role', 'presentation');
- canvas.width = 68;
- canvas.height = 68;
-
- icon.appendChild(canvas);
-
var img = this.img = new Image();
- img.src = this.descriptor.icon;
-
- var self = this;
- img.onload = function icon_loadSuccess() {
- self.generateShadow(canvas, img);
+ img.setAttribute('role', 'presentation');
+ img.width = 68;
+ img.height = 68;
+ img.style.visibility = 'hidden';
+ icon.appendChild(img);
+ if (descriptor.renderedIcon) {
+ this.displayRenderedIcon();
+ } else {
+ this.fetchImageData();
}
- img.onerror = function icon_loadError() {
- img.src = '//' + window.location.host + '/resources/images/Unknown.png';
- img.onload = function icon_errorIconLoadSucess() {
- self.generateShadow(canvas, img);
- }
- };
-
// Label
// wrapper of the label -> overflow text should be centered
@@ -82,26 +88,94 @@ Icon.prototype = {
var wrapper = document.createElement('span');
wrapper.className = 'labelWrapper';
var label = this.label = document.createElement('span');
- label.textContent = this.descriptor.name;
+ label.textContent = localizedName;
wrapper.appendChild(label);
icon.appendChild(wrapper);
container.appendChild(icon);
- if (!this.descriptor.isCore) {
+ if (descriptor.removable) {
// Menu button to delete the app
var options = document.createElement('span');
options.className = 'options';
- options.dataset.origin = this.descriptor.origin;
container.appendChild(options);
}
target.appendChild(container);
},
- generateShadow: function(canvas, img) {
+ fetchImageData: function icon_fetchImageData() {
+ var descriptor = this.descriptor;
+ var icon = descriptor.icon;
+ if (!icon) {
+ self.loadImageData();
+ return;
+ }
+
+ // If we already have locally cached data, load the image right away.
+ if (icon.indexOf('data:') == 0) {
+ this.loadImageData();
+ return;
+ }
+
+ var self = this;
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open('GET', icon, true);
+ xhr.responseType = 'blob';
+ xhr.send(null);
+
+ xhr.onreadystatechange = function saveIcon_readyStateChange(evt) {
+ if (xhr.readyState != xhr.DONE)
+ return;
+
+ if (xhr.status != 0 && xhr.status != 200) {
+ self.loadImageData();
+ return;
+ }
+ self.loadImageData(xhr.response);
+ };
+
+ xhr.onerror = function saveIcon_onerror() {
+ self.loadImageData();
+ };
+ },
+
+ loadImageData: function icon_loadImageData(blob) {
+ var self = this;
+ var img = new Image();
+ if (blob) {
+ var url = window.URL.createObjectURL(blob);
+ img.src = url;
+ } else {
+ img.src = this.descriptor.icon;
+ }
+
+ img.onload = function icon_loadSuccess() {
+ if (blob)
+ window.URL.revokeObjectURL(img.src);
+
+ self.renderImage(img);
+ };
+
+ img.onerror = function icon_loadError() {
+ if (blob)
+ window.URL.revokeObjectURL(img.src);
+
+ img.src = self.DEFAULT_ICON_URL;
+ img.onload = function icon_errorIconLoadSucess() {
+ self.renderImage(img);
+ };
+ };
+ },
+
+ renderImage: function icon_renderImage(img) {
+ var canvas = document.createElement('canvas');
+ canvas.width = 68;
+ canvas.height = 68;
+
var ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 2;
ctx.shadowOffsetY = 2;
@@ -119,9 +193,37 @@ Icon.prototype = {
(canvas.height - height) / 2,
width, height);
ctx.fill();
+
+ var self = this;
+ canvas.toBlob(function canvasAsBlob(blob) {
+ self.descriptor.renderedIcon = blob;
+ GridManager.markDirtyState();
+ self.displayRenderedIcon();
+ });
+ },
+
+ displayRenderedIcon: function icon_displayRenderedIcon(img, skipRevoke) {
+ img = img || this.img;
+ var url = window.URL.createObjectURL(this.descriptor.renderedIcon);
+ img.src = url;
+ var self = this;
+ img.onload = img.onerror = function cleanup() {
+ img.style.visibility = 'visible';
+ if (!skipRevoke)
+ window.URL.revokeObjectURL(url);
+ if (self.needsShow)
+ self.show();
+ };
},
show: function icon_show() {
+ // Wait for the icon image to load until we start the animation.
+ if (!this.img.naturalWidth) {
+ this.needsShow = true;
+ return;
+ }
+
+ this.needsShow = false;
var container = this.container;
container.dataset.visible = true;
container.addEventListener('animationend', function animationEnd(e) {
@@ -130,12 +232,61 @@ Icon.prototype = {
});
},
+ update: function icon_update(descriptor, app) {
+ this.app = app;
+ var oldDescriptor = this.descriptor;
+ this.descriptor = descriptor;
+
+ if (descriptor.icon == oldDescriptor.icon) {
+ this.descriptor.renderedIcon = oldDescriptor.renderedIcon;
+ } else {
+ this.fetchImageData();
+ }
+ if (descriptor.name != oldDescriptor.name ||
+ descriptor.localizedName != oldDescriptor.localizedName) {
+ this.translate();
+ }
+ },
+
+ remove: function icon_remove() {
+ this.container.parentNode.removeChild(this.container);
+ },
+
/*
* Translates the label of the icon
*/
translate: function icon_translate() {
- var desc = this.descriptor;
- this.label.textContent = desc.name = Applications.getName(desc.origin);
+ var descriptor = this.descriptor;
+ if (descriptor.bookmarkURL)
+ return;
+
+ var app = this.app;
+ if (!app)
+ return;
+
+ var manifest = app.manifest;
+ if (!manifest)
+ return;
+
+ var iconsAndNameHolder = manifest;
+ var entryPoint = descriptor.entry_point;
+ if (entryPoint)
+ iconsAndNameHolder = manifest.entry_points[entryPoint];
+
+ var localizedName = iconsAndNameHolder.name;
+ var locales = iconsAndNameHolder.locales;
+ if (locales) {
+ var locale = locales[document.documentElement.lang];
+ if (locale && locale.name) {
+ localizedName = locale.name;
+ }
+ }
+
+ this.label.textContent = localizedName;
+ if (descriptor.localizedName != localizedName) {
+ descriptor.localizedName = localizedName;
+ GridManager.markDirtyState();
+ }
},
/*
@@ -152,8 +303,19 @@ Icon.prototype = {
var draggableElem = this.draggableElem = document.createElement('div');
draggableElem.className = 'draggable';
- draggableElem.appendChild(this.icon.cloneNode());
- this.generateShadow(draggableElem.querySelector('canvas'), this.img);
+ // For some reason, cloning and moving a node re-triggers the blob
+ // URI to be validated. So we assign a new blob URI to the image
+ // and don't revoke it until we're finished with the animation.
+ var skipRevoke = true;
+ this.displayRenderedIcon(this.img, skipRevoke);
+
+ var icon = this.icon.cloneNode();
+ var img = icon.querySelector('img');
+ img.style.visibility = "hidden";
+ img.onload = img.onerror = function unhide() {
+ img.style.visibility = "visible";
+ };
+ draggableElem.appendChild(icon);
var container = this.container;
container.dataset.dragging = 'true';
@@ -213,6 +375,8 @@ Icon.prototype = {
draggableElem.removeEventListener('transitionend', draggableEnd);
delete container.dataset.dragging;
document.body.removeChild(draggableElem);
+ var img = draggableElem.querySelector('img');
+ window.URL.revokeObjectURL(img.src);
callback();
});
},
@@ -223,18 +387,22 @@ Icon.prototype = {
getLeft: function icon_getLeft() {
return this.container.getBoundingClientRect().left;
- },
-
- getOrigin: function icon_getOrigin() {
- return this.descriptor.origin;
}
};
/*
* Page constructor
+ *
+ * @param {HTMLElement] container
+ * HTML container element of the page.
+ *
+ * @param {Array} icons [optional]
+ * List of Icon objects.
*/
-var Page = function(index) {
- this.icons = {};
+function Page(container, icons) {
+ this.container