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
147
gaia/shared/js/homescreens/confirm_dialog_helper.js
Executable file
147
gaia/shared/js/homescreens/confirm_dialog_helper.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
/* global LazyLoader */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var resources = ['/shared/js/component_utils.js',
|
||||
'/shared/elements/gaia_buttons/script.js',
|
||||
'/shared/elements/gaia_confirm/script.js',
|
||||
'/shared/elements/gaia_menu/script.js'];
|
||||
/**
|
||||
Generic dialog helper this _depends_ on <gaia-confirm> but is not required
|
||||
to be loaded as part of the main gaia-confirm script.
|
||||
*/
|
||||
function ConfirmDialogHelper(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
ConfirmDialogHelper.prototype = {
|
||||
show: function(parent) {
|
||||
LazyLoader.load(resources, this._show.bind(this, parent));
|
||||
},
|
||||
|
||||
_show: function(parent) {
|
||||
var config = this.config;
|
||||
var wrapper = document.createElement('div');
|
||||
|
||||
// only include a cancel button if the config was given...
|
||||
var cancelButton = config.cancel ?
|
||||
'<button class="cancel" type="button"></button>' :
|
||||
'';
|
||||
|
||||
wrapper.innerHTML =
|
||||
'<gaia-confirm>' +
|
||||
'<h1></h1>' +
|
||||
'<p></p>' +
|
||||
'<gaia-buttons skin="dark">' +
|
||||
cancelButton +
|
||||
'<button class="confirm" type="button"></button>' +
|
||||
'</gaia-buttons>' +
|
||||
'</gaia-confirm>';
|
||||
|
||||
var element = wrapper.firstElementChild;
|
||||
|
||||
this.element = element;
|
||||
|
||||
element.dataset.type = config.type;
|
||||
element.addEventListener('confirm', this);
|
||||
element.addEventListener('cancel', this);
|
||||
|
||||
// XXX: Primarily here for pressing the home screen button.
|
||||
// The home button triggers a hashchange of the homescreen.
|
||||
window.addEventListener('hashchange', this);
|
||||
// TODO: Add visibility change handling...
|
||||
|
||||
var title = element.querySelector('h1');
|
||||
var body = element.querySelector('p');
|
||||
var cancel = element.querySelector('.cancel');
|
||||
var confirm = element.querySelector('.confirm');
|
||||
|
||||
var setL10nAttributes = function (element, options){
|
||||
if ('string' === typeof options) {
|
||||
navigator.mozL10n.setAttributes(element, options);
|
||||
}
|
||||
|
||||
if(options.id) {
|
||||
navigator.mozL10n.setAttributes(element, options.id, options.args);
|
||||
}
|
||||
};
|
||||
|
||||
setL10nAttributes(title, config.title);
|
||||
setL10nAttributes(body, config.body);
|
||||
setL10nAttributes(confirm, config.confirm.title);
|
||||
|
||||
if (config.cancel) {
|
||||
setL10nAttributes(cancel, config.cancel.title);
|
||||
}
|
||||
|
||||
if (config.confirm.type) {
|
||||
confirm.classList.add(config.confirm.type);
|
||||
}
|
||||
|
||||
document.activeElement.blur();
|
||||
element.setAttribute('hidden', '');
|
||||
parent.appendChild(element);
|
||||
|
||||
// This nested requestAnimationFrame is to work around the coalescing
|
||||
// of the style changes associated with removing of the 'hidden'
|
||||
// attribute with the creation of the element.
|
||||
// For whatever reason, flushing the style with the usual trick of
|
||||
// accessing clientTop doesn't work, and a setTimeout requires an
|
||||
// unreasonably lengthy timeout (>50ms) to work, and that may not be
|
||||
// a reliable solution.
|
||||
// This work-around, though gross, appears to work consistently without
|
||||
// introducing too much lag or extra work.
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
element.removeAttribute('hidden');
|
||||
window.dispatchEvent(new CustomEvent('gaia-confirm-open'));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure cleanup of our hacks!
|
||||
window.removeEventListener('hashchange', this);
|
||||
|
||||
this.element.addEventListener('transitionend',
|
||||
function removeAfterHide(e) {
|
||||
if (e.target !== this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
this.element = null;
|
||||
window.dispatchEvent(new CustomEvent('gaia-confirm-close'));
|
||||
}.bind(this));
|
||||
|
||||
this.element.setAttribute('hidden', '');
|
||||
},
|
||||
|
||||
handleEvent: function(e) {
|
||||
// Ensure we hide the dialog in the face of other errors.
|
||||
this.destroy();
|
||||
|
||||
switch (e.type) {
|
||||
case 'hashchange':
|
||||
// Hashchange is only here to trigger this to call destroy.
|
||||
break;
|
||||
case 'confirm':
|
||||
var confirm = this.config.confirm.cb;
|
||||
confirm && confirm();
|
||||
break;
|
||||
case 'cancel':
|
||||
var cancel = this.config.cancel.cb;
|
||||
cancel && cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This name is intentionally verbose.
|
||||
window.ConfirmDialogHelper = ConfirmDialogHelper;
|
||||
}(window));
|
||||
201
gaia/shared/js/homescreens/homescreen_settings.js
Executable file
201
gaia/shared/js/homescreens/homescreen_settings.js
Executable file
|
|
@ -0,0 +1,201 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
/* global Promise */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
var datastore;
|
||||
|
||||
var storeName = 'homescreen_settings';
|
||||
|
||||
// Indicates the initialization state
|
||||
var readyState;
|
||||
|
||||
// Event listeners
|
||||
var listeners = Object.create(null);
|
||||
|
||||
function init() {
|
||||
return new Promise(function doInit(resolve, reject) {
|
||||
if (readyState === 'initialized') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener('vps-initialized', function oninitalized() {
|
||||
document.removeEventListener('vps-initialized', oninitalized);
|
||||
resolve();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'initializing';
|
||||
|
||||
if (!navigator.getDataStores) {
|
||||
console.error('Home screen settings store: ' +
|
||||
'DataStore API is not working');
|
||||
reject({ name: 'NO_DATASTORE' });
|
||||
readyState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.getDataStores(storeName).then(function(ds) {
|
||||
if (ds.length < 1) {
|
||||
console.error('Home screen settings store: ' +
|
||||
'Cannot get access to the Store');
|
||||
reject({ name: 'NO_ACCESS_TO_DATASTORE' });
|
||||
readyState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
datastore = ds[0];
|
||||
datastore.addEventListener('change', onchangeHandler);
|
||||
readyState = 'initialized';
|
||||
document.dispatchEvent(new CustomEvent('vps-initialized'));
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function onchangeHandler(event) {
|
||||
var operation = event.operation;
|
||||
var callbacks = listeners[operation];
|
||||
callbacks && callbacks.forEach(function iterCallback(callback) {
|
||||
datastore.get(event.id).then(function got(result) {
|
||||
callback.method.call(callback.context || this, {
|
||||
type: operation,
|
||||
target: {
|
||||
name: event.id,
|
||||
value: result
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addEventListener(type, callback) {
|
||||
var context;
|
||||
if (!(type in listeners)) {
|
||||
listeners[type] = [];
|
||||
}
|
||||
|
||||
var cb = callback;
|
||||
if (typeof cb === 'object') {
|
||||
context = cb;
|
||||
cb = cb.handleEvent;
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
listeners[type].push({
|
||||
method: cb,
|
||||
context: context
|
||||
});
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
function removeEventListener(type, callback) {
|
||||
if (!(type in listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var callbacks = listeners[type];
|
||||
var length = callbacks.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
|
||||
var thisCallback = callback;
|
||||
if (typeof thisCallback === 'object') {
|
||||
thisCallback = callback.handleEvent;
|
||||
}
|
||||
|
||||
if (callbacks[i] && callbacks[i].method === thisCallback) {
|
||||
callbacks.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
return new Promise(function doGet(resolve, reject) {
|
||||
init().then(function onInitialized() {
|
||||
datastore.get(id).then(resolve, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function put(id, value) {
|
||||
return new Promise(function doAdd(resolve, reject) {
|
||||
init().then(function onInitialized() {
|
||||
datastore.put(value, id).then(function success() {
|
||||
resolve(); // Setting was updated
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function setStoreName(name) {
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener('vps-initialized',
|
||||
function deferSetStoreName() {
|
||||
document.removeEventListener('vps-initialized', deferSetStoreName);
|
||||
setStoreName(name);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'uninitialized';
|
||||
storeName = name;
|
||||
}
|
||||
|
||||
exports.homescreenSettings = {
|
||||
/*
|
||||
* This method returns a setting value
|
||||
*
|
||||
* @param{String} String param that represents an identifier
|
||||
*/
|
||||
get: get,
|
||||
|
||||
/*
|
||||
* This method updates a setting in the datastore
|
||||
*
|
||||
* @param{String} The setting identifier
|
||||
*
|
||||
* @param{Object} The setting value
|
||||
*/
|
||||
put: put,
|
||||
|
||||
/*
|
||||
* Method registers the specified listener on the API
|
||||
*
|
||||
* @param{String} A string representing the event type to listen for
|
||||
*
|
||||
* @param{Function} The method that receives a notification when an event of
|
||||
* the specified type occurs
|
||||
*
|
||||
*/
|
||||
addEventListener: addEventListener,
|
||||
|
||||
/*
|
||||
* Method removes the specified listener on the API
|
||||
*
|
||||
* @param{String} A string representing the event type to listen for
|
||||
*
|
||||
* @param{Function} The method that received a notification when an event of
|
||||
* the specified type occurs
|
||||
*
|
||||
*/
|
||||
removeEventListener: removeEventListener,
|
||||
|
||||
/*
|
||||
* Method allows setting an alternative datastore. Must be called before
|
||||
* use of any other methods.
|
||||
*
|
||||
* @param{String} The name of the alternative datastore to use
|
||||
*/
|
||||
setStoreName: setStoreName
|
||||
};
|
||||
|
||||
}(window));
|
||||
137
gaia/shared/js/homescreens/icon.js
Executable file
137
gaia/shared/js/homescreens/icon.js
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
'use strict';
|
||||
/* global GridIconRenderer, devicePixelRatio, Promise */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const FETCH_XHR_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* XHR wrapper for fetching blobs with timeout logic.
|
||||
*
|
||||
* @param {String} uri to fetch.
|
||||
* @return {Promise[Blob]}
|
||||
*/
|
||||
function fetchBlob(uri) {
|
||||
return new Promise(function(accept, reject) {
|
||||
var xhr = new XMLHttpRequest({
|
||||
mozAnon: true,
|
||||
mozSystem: true
|
||||
});
|
||||
|
||||
xhr.open('GET', uri, true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.timeout = FETCH_XHR_TIMEOUT;
|
||||
xhr.send();
|
||||
|
||||
xhr.onload = function() {
|
||||
var status = xhr.status;
|
||||
if (status !== 0 && status !== 200) {
|
||||
reject(
|
||||
new Error('Got HTTP status ' + status + ' trying to load ' + uri)
|
||||
);
|
||||
return;
|
||||
}
|
||||
accept(xhr.response);
|
||||
};
|
||||
|
||||
xhr.onerror = xhr.ontimeout = function() {
|
||||
reject(new Error('Error while HTTP GET: ', uri));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a generic icon.
|
||||
*
|
||||
* @param {DOMElement} The DOM element.
|
||||
* @param {String} icon uri.
|
||||
*/
|
||||
function Icon(elem, uri) {
|
||||
this.elem = elem;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
Icon.prototype = {
|
||||
/**
|
||||
* The size icon by default.
|
||||
*/
|
||||
get size() {
|
||||
return this._size || 40;
|
||||
},
|
||||
|
||||
set size(size) {
|
||||
this._size = size || this.size;
|
||||
var style = this.elem.style;
|
||||
var sizeInRems = (this._size / 10) + 'rem';
|
||||
style.backgroundSize = style.width = style.height = sizeInRems;
|
||||
},
|
||||
|
||||
/**
|
||||
* It renders the icon. If the URI of the icon is not defined or there is an
|
||||
* error fetching this one, the icon by default will be displayed if it is
|
||||
* defined in the stylesheet.
|
||||
*
|
||||
* @param {Object} It defines two optional parameters. "size" which defines
|
||||
* the size of the icon and "type" that describes how the
|
||||
* icon will be rendered ('clip', 'favicon' or 'standard').
|
||||
* See GridIconRenderer.
|
||||
*/
|
||||
render: function render(options) {
|
||||
options = options || {};
|
||||
var uri = this.uri;
|
||||
this.size = options.size;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchBlob(uri).then(function(blob) {
|
||||
this.renderBlob(blob, options);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
renderBlob: function renderBlob(blob, options) {
|
||||
options = options || {};
|
||||
var style = this.elem.style;
|
||||
this.size = options.size;
|
||||
var img = new Image();
|
||||
img.src = URL.createObjectURL(blob);
|
||||
var size = this.size;
|
||||
|
||||
img.onload = () => {
|
||||
var renderer = new GridIconRenderer({
|
||||
grid: {
|
||||
layout: {
|
||||
get gridIconSize() {
|
||||
return size;
|
||||
},
|
||||
get gridMaxIconSize() {
|
||||
return size * devicePixelRatio;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var type = options.type || GridIconRenderer.TYPE.FAVICON;
|
||||
renderer[type](img).then((blob) => {
|
||||
var url = URL.createObjectURL(blob);
|
||||
var imageUrl = 'url(' + url + ')';
|
||||
style.backgroundImage = imageUrl;
|
||||
if (options.onLoad) {
|
||||
options.onLoad(blob);
|
||||
}
|
||||
});
|
||||
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
if (options.onerror) {
|
||||
options.onerror();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
exports.Icon = Icon;
|
||||
|
||||
}(window));
|
||||
177
gaia/shared/js/homescreens/vertical_preferences.js
Executable file
177
gaia/shared/js/homescreens/vertical_preferences.js
Executable file
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
/* global Promise */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
var datastore;
|
||||
|
||||
var DATASTORE_NAME = 'vertical_preferences_store';
|
||||
|
||||
// Indicates the initialization state
|
||||
var readyState;
|
||||
|
||||
// Event listeners
|
||||
var listeners = Object.create(null);
|
||||
|
||||
function init() {
|
||||
return new Promise(function doInit(resolve, reject) {
|
||||
if (readyState === 'initialized') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener('vps-initialized', function oninitalized() {
|
||||
document.removeEventListener('vps-initialized', oninitalized);
|
||||
resolve();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'initializing';
|
||||
|
||||
if (!navigator.getDataStores) {
|
||||
console.error('Preferences store: DataStore API is not working');
|
||||
reject({ name: 'NO_DATASTORE' });
|
||||
readyState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.getDataStores(DATASTORE_NAME).then(function(ds) {
|
||||
if (ds.length < 1) {
|
||||
console.error('Preferences store: Cannot get access to the Store');
|
||||
reject({ name: 'NO_ACCESS_TO_DATASTORE' });
|
||||
readyState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
datastore = ds[0];
|
||||
datastore.addEventListener('change', onchangeHandler);
|
||||
readyState = 'initialized';
|
||||
document.dispatchEvent(new CustomEvent('vps-initialized'));
|
||||
resolve();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function onchangeHandler(event) {
|
||||
var operation = event.operation;
|
||||
var callbacks = listeners[operation];
|
||||
callbacks && callbacks.forEach(function iterCallback(callback) {
|
||||
datastore.get(event.id).then(function got(result) {
|
||||
callback.method.call(callback.context || this, {
|
||||
type: operation,
|
||||
target: {
|
||||
name: event.id,
|
||||
value: result
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addEventListener(type, callback) {
|
||||
var context;
|
||||
if (!(type in listeners)) {
|
||||
listeners[type] = [];
|
||||
}
|
||||
|
||||
var cb = callback;
|
||||
if (typeof cb === 'object') {
|
||||
context = cb;
|
||||
cb = cb.handleEvent;
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
listeners[type].push({
|
||||
method: cb,
|
||||
context: context
|
||||
});
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
function removeEventListener(type, callback) {
|
||||
if (!(type in listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var callbacks = listeners[type];
|
||||
var length = callbacks.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
|
||||
var thisCallback = callback;
|
||||
if (typeof thisCallback === 'object') {
|
||||
thisCallback = callback.handleEvent;
|
||||
}
|
||||
|
||||
if (callbacks[i] && callbacks[i].method === thisCallback) {
|
||||
callbacks.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
return new Promise(function doGet(resolve, reject) {
|
||||
init().then(function onInitialized() {
|
||||
datastore.get(id).then(resolve, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function put(id, value) {
|
||||
return new Promise(function doAdd(resolve, reject) {
|
||||
init().then(function onInitialized() {
|
||||
datastore.put(value, id).then(function success() {
|
||||
resolve(); // Preference was updated
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
exports.verticalPreferences = {
|
||||
/*
|
||||
* This method returns a preference value
|
||||
*
|
||||
* @param{String} String param that represents an identifier
|
||||
*/
|
||||
get: get,
|
||||
|
||||
/*
|
||||
* This method updates a preference in the datastore
|
||||
*
|
||||
* @param{String} The preference identifier
|
||||
*
|
||||
* @param{Object} The preference value
|
||||
*/
|
||||
put: put,
|
||||
|
||||
/*
|
||||
* Method registers the specified listener on the API
|
||||
*
|
||||
* @param{String} A string representing the event type to listen for
|
||||
*
|
||||
* @param{Function} The method that receives a notification when an event of
|
||||
* the specified type occurs
|
||||
*
|
||||
*/
|
||||
addEventListener: addEventListener,
|
||||
|
||||
/*
|
||||
* Method removes the specified listener on the API
|
||||
*
|
||||
* @param{String} A string representing the event type to listen for
|
||||
*
|
||||
* @param{Function} The method that received a notification when an event of
|
||||
* the specified type occurs
|
||||
*
|
||||
*/
|
||||
removeEventListener: removeEventListener
|
||||
};
|
||||
|
||||
}(window));
|
||||
Loading…
Reference in a new issue