define(function(require) { 'use strict'; var AppsCache = require('modules/apps_cache'); var ManifestHelper = require('shared/manifest_helper'); var SettingsCache = require('modules/settings_cache'); var ListView = require('modules/mvvm/list_view'); var template = require('./layout_template'); var THEME_SELECTED = 'theme.selected'; var WALLPAPER_LIST = '/wallpaper.json'; var WALLPAPER_KEY = 'wallpaper.image'; var Themes = function ctor_themes() { return { _container: null, _settings: null, _selectedTheme: null, _previousTheme: null, _themes: null, _config: {}, onInit: function th_onInit(panel) { this._container = panel.querySelector('.theme-list'); this._settings = navigator.mozSettings; }, onBeforeShow: function th_onBeforeShow() { this._themes = []; this.getInstalledThemes((this.setTheme)); }, getInstalledThemes: function th_getInstalledThemes(callback) { AppsCache.apps().then(function(apps) { apps = apps.filter(function(app) { var manifest = app.manifest || app.updateManifest; return manifest && manifest.type && manifest.type === 'certified' && manifest.role && manifest.role === 'theme'; }); for (var app in apps) { var manifest = new ManifestHelper(apps[app].manifest); var theme = { 'name': manifest.name, 'manifestURL': apps[app].manifestURL, 'onclick': callback.bind(this) }; this._themes.push(theme); } this._themes.sort(function(a, b) { return a.name.localeCompare(b.name); }); this.renderThemes(); }.bind(this)); }, renderThemes: function th_renderThemes() { this._listView = ListView(this._container, this._themes, template); this.updateRadioButtons(); }, updateRadioButtons: function th_updateRadioButtons() { var currentSetting = SettingsCache.cache; if (!currentSetting) { return; } var theme = this._selectedTheme = currentSetting[THEME_SELECTED]; this._updateRow(theme, true); }, _doSetTheme: function th_doSetTheme(theme) { if (this._selectedTheme === theme) { return Promise.resolve(); } return new Promise((function(resolve, reject) { var setting = {}; this._previousTheme = this._selectedTheme; setting[THEME_SELECTED] = this._selectedTheme = theme; var req = this._settings.createLock().set(setting); req.onsuccess = (function() { this.getWallpaperPath(). then((this.loadWallpaper).bind(this)). then((this.setWallpaper).bind(this)). then((this.saveConfig).bind(this)).then(resolve, reject); }).bind(this); req.onerror = reject; }).bind(this)); }, setTheme: function th_setTheme(theme) { this.disableSelection(); return this._doSetTheme(theme).then(this.enableSelection.bind(this), this.rollbackTheme.bind(this)); }, /** * Setting a theme will happen in a two steps process, we will first * set the theme manifest, and after that we will save any other setting * related to the new theme. * If an error happens in any of those two processes, we will need to * be sure that we enable back the previous theme selection */ rollbackTheme: function th_rollbackTheme() { this._config = {}; if (this._previousTheme === this._selectedTheme || this._previousTheme === null) { return Promise.reject('No previous theme to rollback'); } var previous = '' + this._previousTheme; var current = '' + this._selectedTheme; return this._doSetTheme(this._previousTheme).then((function() { this._updateRow(previous, true); this._updateRow(current, false); this.enableSelection(); }).bind(this)); }, _updateRow: function th_updateRow(theme, checked) { var rule = 'gaia-radio[value="' + theme + '"]'; var node = this._container.querySelector(rule); if (node) { node.checked = !!checked; } }, disableSelection: function th_disableSelection() { var nodes = this._container.querySelectorAll('gaia-radio:not([value="' + this._selectedTheme + '"])'); if (nodes) { Array.prototype.slice.call(nodes).forEach(function(node) { node.parentNode.parentNode.classList.add('disabled'); }); } }, enableSelection: function th_enableSelection() { var nodes = this._container.querySelectorAll('gaia-radio'); if (nodes) { Array.prototype.slice.call(nodes).forEach(function(node) { node.parentNode.parentNode.classList.remove('disabled'); }); } }, /** * Given the current them set, we get the path for the * image set as background. * @returns {Promise} fulfilled with the path to the wallpaper */ getWallpaperPath: function th_getWallpaper() { var url = this._selectedTheme .substring(0, this._selectedTheme.lastIndexOf('/')) + WALLPAPER_LIST; var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('GET', url, true); xhr.responseType = 'json'; return new Promise(function(resolve, reject) { xhr.onload = function successGettingWallpaperList() { if (xhr.status !== 200) { reject(xhr.status); return; } var filename = xhr.response.homescreen; resolve(filename); }; xhr.send(null); }); }, /** * Load an image {blob} from another app via XHR. * @params filename {String} path to the wallpaper on current theme. * @returns {Promise} fulfilled with the blob. */ loadWallpaper: function th_loadWallpaper(filename) { if (!filename) { return Promise.resolve(null); } var url = this._selectedTheme.substring(0, this._selectedTheme .lastIndexOf('/')) + filename; var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('GET', url, true); xhr.responseType = 'blob'; return new Promise(function(resolve, reject) { xhr.onload = function() { if (xhr.status !== 200) { reject(xhr.status); return; } resolve(xhr.response); }; xhr.send(null); }); }, /** * Saves in memory the configuration for wallpaper. saveConfig * will need to be invoked to make this changes permanent. * @returns {Promise} fulfilled inmediately. */ setWallpaper: function th_setWallpaper(blob) { this._config[WALLPAPER_KEY] = blob; return Promise.resolve(this._config); }, /** * Perform the operation to writing to settings * @returns {Promise} fulfilled when config is saved */ saveConfig: function th_saveConfig() { var self = this; return new Promise(function(resolve, reject) { var request = self._settings.createLock().set(self._config); request.onerror = reject; request.onsuccess = resolve; }); } }; }; return Themes; });