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.
1
gaia/apps/homescreen/.gitignore
vendored
Executable file
|
|
@ -0,0 +1 @@
|
|||
bower_components/gaia-component
|
||||
24
gaia/apps/homescreen/bower.json
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "Home screen",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/apps/homescreen",
|
||||
"authors": [
|
||||
"Chris Lord <chrislord.net@gmail.com>",
|
||||
"Guillaume Marty <edo999@gmail.com>"
|
||||
],
|
||||
"main": "index.html",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components"
|
||||
],
|
||||
"dependencies": {
|
||||
"base": "fxos-components/base#master",
|
||||
"evt": "fxos-components/evt#master",
|
||||
"gaia-container": "https://gitlab.com/Cwiiis/gaia-container.git",
|
||||
"gaia-dialog": "fxos-components/gaia-dialog#master",
|
||||
"pressed": "fxos-components/pressed#master"
|
||||
}
|
||||
}
|
||||
25
gaia/apps/homescreen/bower_components/base/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "base",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "base.css",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"homepage": "https://github.com/gaia-components/base",
|
||||
"_release": "737e000325",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "737e0003253ee4f3ceff5c1223543a1b3f7d39dd"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/base.git",
|
||||
"_target": "master",
|
||||
"_originalSource": "gaia-components/base"
|
||||
}
|
||||
227
gaia/apps/homescreen/bower_components/base/base.css
vendored
Executable file
|
|
@ -0,0 +1,227 @@
|
|||
|
||||
/** Variables
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:root {
|
||||
--dim-micro: 6px;
|
||||
--dim-small: 12px;
|
||||
--dim-medium: 24px;
|
||||
--dim-mega: 48px;
|
||||
|
||||
--base-xs: 6px;
|
||||
--base-s: 18px;
|
||||
--base-m: 24px;
|
||||
--base-l: 30px;
|
||||
--base-xl: 42px;
|
||||
--base-xxl: 60px;
|
||||
|
||||
--base-font-xs: 10px;
|
||||
--base-font-s: 14px;
|
||||
--base-font-m: 17px;
|
||||
--base-font-l: 23px;
|
||||
--base-font-xl: 60px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Apply a natural box layout
|
||||
* model to all elements
|
||||
*/
|
||||
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: border-box; /* 1 */
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Document
|
||||
---------------------------------------------------------*/
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Body
|
||||
---------------------------------------------------------*/
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
font: 300 17px/21px 'FiraSans';
|
||||
background-color: var(--background);
|
||||
color: var(--text-color);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Tags
|
||||
---------------------------------------------------------*/
|
||||
|
||||
a {
|
||||
color: var(--highlight-color, inherit);
|
||||
text-decoration: none;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
margin: var(--dim-small) 0 var(--dim-small) 0;
|
||||
border: 0;
|
||||
background:
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
rgba(0,0,0,0.2)));
|
||||
}
|
||||
|
||||
em {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.alpha {
|
||||
font-size: 23px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
line-height: 26px;
|
||||
margin: 6px 0 24px 0;
|
||||
}
|
||||
|
||||
.beta {
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
margin: 32px 0 16px 0;
|
||||
}
|
||||
|
||||
.gamma {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
margin: 32px 0 16px 0;
|
||||
}
|
||||
|
||||
.milli {
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.mega {
|
||||
font-size: 60px;
|
||||
line-height: 60px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.giga {}
|
||||
.kilo {}
|
||||
|
||||
/* alignment */
|
||||
.l-align-right { float: right; }
|
||||
.l-align-left { float: left; }
|
||||
.l-align-center { text-align: center; }
|
||||
.l-text-align-left { text-align: left; }
|
||||
.l-text-align-right { text-align: right; }
|
||||
.l-text-valign-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.l-width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/** Flexbox
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-flex { display: flex; }
|
||||
.l-flex-col { display: flex; flex-direction: column; }
|
||||
|
||||
.l-flex-col-v-align {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.l-flex-v-align { align-items: center; }
|
||||
.l-flex-h-align { justify-content: center; }
|
||||
.l-flex-grow { flex: 1; }
|
||||
|
||||
/** Margin
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/* Top */
|
||||
|
||||
.l-mt-small { margin-top: var(--dim-small); }
|
||||
.l-mt-med { margin-top: var(--dim-medium); }
|
||||
|
||||
/* Bottom */
|
||||
|
||||
.l-mb-small { margin-bottom: var(--dim-small); }
|
||||
.l-mb-med { margin-bottom: var(--dim-medium); }
|
||||
.l-mb-large {}
|
||||
.l-mb-mega { margin-bottom: var(--dim-mega); }
|
||||
|
||||
/* Left */
|
||||
|
||||
.l-ml-small { margin-left: var(--dim-small); }
|
||||
|
||||
/* Right */
|
||||
|
||||
.l-mr-small { margin-right: var(--dim-small); }
|
||||
|
||||
/* Auto */
|
||||
|
||||
.l-mlr-auto { margin-left: auto; margin-right: auto; }
|
||||
.l-m-auto { margin: auto }
|
||||
|
||||
/* Margin */
|
||||
|
||||
.l-m-small { margin: var(--dim-small) }
|
||||
.l-m-med { margin: var(--dim-medium) }
|
||||
.l-m-0 { margin: 0 }
|
||||
|
||||
/** Padding
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-p-small { padding: var(--dim-small); }
|
||||
.l-pl-small { padding-left: var(--dim-small); }
|
||||
.l-pr-small { padding-right: var(--dim-small); }
|
||||
.l-plr-small {
|
||||
padding-right: var(--dim-small);
|
||||
padding-left: var(--dim-small);
|
||||
}
|
||||
.l-pb-small { padding-bottom: var(--dim-small); }
|
||||
.l-pt-small { padding-top: var(--dim-small); }
|
||||
.l-pt-mega { padding-top: var(--dim-mega); }
|
||||
.l-pt-micro { padding-top: var(--dim-micro); }
|
||||
.l-pb-micro { padding-bottom: var(--dim-micro); }
|
||||
.l-p-0 { padding: 0 ;}
|
||||
|
||||
/** Helpers
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.l-inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.is-invisible {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
16
gaia/apps/homescreen/bower_components/base/bower.json
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "base",
|
||||
"version": "0.0.0",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "base.css",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
27
gaia/apps/homescreen/bower_components/evt/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "evt",
|
||||
"main": "index.js",
|
||||
"homepage": "https://github.com/wilsonpage/evt",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"package.json",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
],
|
||||
"_release": "3593dd6b4d",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "3593dd6b4dd19f683d00fd0eaf2581c11a128da6"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/evt.git",
|
||||
"_target": "master",
|
||||
"_originalSource": "gaia-components/evt"
|
||||
}
|
||||
19
gaia/apps/homescreen/bower_components/evt/bower.json
vendored
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "evt",
|
||||
"main": "index.js",
|
||||
"version": "0.4.0",
|
||||
"homepage": "https://github.com/wilsonpage/evt",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"package.json",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
154
gaia/apps/homescreen/bower_components/evt/index.js
vendored
Executable file
|
|
@ -0,0 +1,154 @@
|
|||
|
||||
/**
|
||||
* Evt
|
||||
*
|
||||
* A super lightweight
|
||||
* event emitter library.
|
||||
*
|
||||
* @version 0.3.3
|
||||
* @author Wilson Page <wilson.page@me.com>
|
||||
*/
|
||||
|
||||
;(function() {
|
||||
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
|
||||
var proto = Events.prototype;
|
||||
var slice = [].slice;
|
||||
|
||||
/**
|
||||
* Creates a new event emitter
|
||||
* instance, or if passed an
|
||||
* object, mixes the event logic
|
||||
* into it.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
function Events(obj) {
|
||||
if (!(this instanceof Events)) return new Events(obj);
|
||||
if (obj) return mixin(obj, proto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback
|
||||
* with an event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.on = function(name, cb) {
|
||||
this._cbs = this._cbs || {};
|
||||
(this._cbs[name] || (this._cbs[name] = [])).push(cb);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach a callback that once
|
||||
* called, detaches itself.
|
||||
*
|
||||
* TODO: Implement `.off()` to work
|
||||
* with `once()` callbacks.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @public
|
||||
*/
|
||||
proto.once = function(name, cb) {
|
||||
this.on(name, one);
|
||||
function one() {
|
||||
cb.apply(this, arguments);
|
||||
this.off(name, one);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a single callback,
|
||||
* or all callbacks associated
|
||||
* with the passed event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.off = function(name, cb) {
|
||||
this._cbs = this._cbs || {};
|
||||
|
||||
if (!name) { this._cbs = {}; return; }
|
||||
if (!cb) { return delete this._cbs[name]; }
|
||||
|
||||
var cbs = this._cbs[name] || [];
|
||||
var i;
|
||||
|
||||
while (cbs && ~(i = cbs.indexOf(cb))) { cbs.splice(i, 1); }
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fires an event, triggering
|
||||
* all callbacks registered on this
|
||||
* event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.fire = proto.emit = function(options) {
|
||||
var cbs = this._cbs = this._cbs || {};
|
||||
var name = options.name || options;
|
||||
var batch = (cbs[name] || []).concat(cbs['*'] || []);
|
||||
var ctx = options.ctx || this;
|
||||
|
||||
if (batch.length) {
|
||||
this._fireArgs = arguments;
|
||||
var args = slice.call(arguments, 1);
|
||||
while (batch.length) {
|
||||
batch.shift().apply(ctx, args);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
proto.firer = function(name) {
|
||||
var self = this;
|
||||
return function() {
|
||||
var args = slice.call(arguments);
|
||||
args.unshift(name);
|
||||
self.fire.apply(self, args);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Util
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mixes in the properties
|
||||
* of the second object into
|
||||
* the first.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @return {Object}
|
||||
*/
|
||||
function mixin(a, b) {
|
||||
for (var key in b) a[key] = b[key];
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose 'Event' (UMD)
|
||||
*/
|
||||
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = Events;
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(function(){ return Events; });
|
||||
} else {
|
||||
window.evt = Events;
|
||||
}
|
||||
|
||||
})();
|
||||
12
gaia/apps/homescreen/bower_components/gaia-container/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "gaia-container",
|
||||
"_release": "16513d8946",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "16513d894678a754a5297772c05d4d761e76a240"
|
||||
},
|
||||
"_source": "https://gitlab.com/Cwiiis/gaia-container.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "https://gitlab.com/Cwiiis/gaia-container.git"
|
||||
}
|
||||
1
gaia/apps/homescreen/bower_components/gaia-container/.gitignore
vendored
Executable file
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
122
gaia/apps/homescreen/bower_components/gaia-container/examples/basic.html
vendored
Executable file
|
|
@ -0,0 +1,122 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
||||
<title>GaiaContainer Demo</title>
|
||||
</head>
|
||||
|
||||
<script src='../script.js'></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div:first-of-type {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div:first-of-type > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
gaia-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
gaia-container.alt {
|
||||
flex-flow: row-reverse wrap-reverse;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child:not(.added):not(.dragging) {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child.dragging {
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child > div {
|
||||
display: inline-block;
|
||||
width: 33vw;
|
||||
height: 33vw;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child:not(.added):not(.removed) > div {
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child.dragging > div {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child.added > div {
|
||||
animation-name: added;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes added {
|
||||
0% { opacity: 0; transform: scale(0.5); }
|
||||
100% { opacity: 100%; }
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child.removed > div {
|
||||
animation-name: removed;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes removed {
|
||||
45% { transform: scaleY(0.1); }
|
||||
55% { transform: scaleY(0.1); }
|
||||
100% { transform: scale(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<button onclick='addBox()'>Add item</button>
|
||||
<button onclick='container.classList.toggle("alt"); container.synchronise()'>Toggle layout</button>
|
||||
</div>
|
||||
|
||||
<gaia-container drag-and-drop></gaia-container>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
var container = document.getElementsByTagName('gaia-container')[0];
|
||||
|
||||
function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
function addBox() {
|
||||
var element = document.createElement('div');
|
||||
|
||||
element.style.backgroundColor = 'rgb(' +
|
||||
randomInt(100, 200) + ',' +
|
||||
randomInt(100, 200) + ',' +
|
||||
randomInt(100, 200) + ')';
|
||||
|
||||
element.onclick = () => {
|
||||
container.removeChild(element);
|
||||
};
|
||||
|
||||
var children = container.children;
|
||||
var insertBefore = children.length ?
|
||||
children[randomInt(0, children.length - 1)] : null
|
||||
container.insertBefore(element, insertBefore);
|
||||
}
|
||||
|
||||
var resizeTimeout = null;
|
||||
window.onresize = () => {
|
||||
if (resizeTimeout !== null) {
|
||||
window.clearTimeout(resizeTimeout);
|
||||
}
|
||||
resizeTimeout = window.setTimeout(() => {
|
||||
resizeTimeout = null;
|
||||
container.synchronise();
|
||||
}, 100);
|
||||
}
|
||||
</script>
|
||||
29
gaia/apps/homescreen/bower_components/gaia-container/package.json
vendored
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "gaia-container",
|
||||
"version": "0.0.0",
|
||||
"description": "Gaia Container is a drop-in replacement for generic container elements that provides convenience methods and behaviour for animations and drag-and-drop functionality.",
|
||||
"main": "script.js",
|
||||
"scripts": {
|
||||
"test": "./node_modules/karma/bin/karma start test/karma.conf.js --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/karma.conf.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@gitlab.com:Cwiiis/gaia-container.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"karma": "^0.12.37",
|
||||
"karma-firefox-launcher": "^0.1.6",
|
||||
"karma-mocha": "^0.2.0",
|
||||
"karma-sinon-chai": "^1.0.0",
|
||||
"mocha": "^2.2.5",
|
||||
"mozilla-download": "^1.1.1"
|
||||
},
|
||||
"keywords": [
|
||||
"container",
|
||||
"homescreen",
|
||||
"B2G",
|
||||
"Firefox OS"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
848
gaia/apps/homescreen/bower_components/gaia-container/script.js
vendored
Executable file
|
|
@ -0,0 +1,848 @@
|
|||
'use strict';
|
||||
|
||||
window.GaiaContainer = (function(exports) {
|
||||
/**
|
||||
* The time, in ms, to wait for an animation to start in response to a state
|
||||
* change, before removing the associated style class.
|
||||
*/
|
||||
const STATE_CHANGE_TIMEOUT = 100;
|
||||
|
||||
/**
|
||||
* The time, in ms, to wait before initiating a drag-and-drop from a
|
||||
* long-press.
|
||||
*/
|
||||
const DEFAULT_DND_TIMEOUT = 300;
|
||||
|
||||
/**
|
||||
* The distance, in CSS pixels, in which a touch or mouse point can deviate
|
||||
* before it being discarded for initiation of a drag-and-drop action.
|
||||
*/
|
||||
const DND_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* The minimum time between sending move events during drag-and-drop.
|
||||
*/
|
||||
const DND_MOVE_THROTTLE = 50;
|
||||
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
proto.createdCallback = function() {
|
||||
this._template = template.content.cloneNode(true);
|
||||
|
||||
var shadow = this.createShadowRoot();
|
||||
shadow.appendChild(this._template);
|
||||
|
||||
this._frozen = false;
|
||||
this._pendingStateChanges = [];
|
||||
this._children = [];
|
||||
this._dnd = {
|
||||
// Whether drag-and-drop is enabled
|
||||
enabled: false,
|
||||
|
||||
// The time, in ms, to wait before initiating a drag-and-drop from a
|
||||
// long-press
|
||||
delay: DEFAULT_DND_TIMEOUT,
|
||||
|
||||
// Timeout used to initiate drag-and-drop actions
|
||||
timeout: null,
|
||||
|
||||
// The child that was tapped/clicked
|
||||
child: null,
|
||||
|
||||
// Whether a drag is active
|
||||
active: false,
|
||||
|
||||
// The start point of the drag action
|
||||
start: { pageX: 0, pageY: 0, clientX: 0, clientY: 0 },
|
||||
|
||||
// The last point of the drag action
|
||||
last: { pageX: 0, pageY: 0, clientX: 0, clientY: 0, timeStamp: 0 },
|
||||
|
||||
// Timeout used to send drag-move events
|
||||
moveTimeout: null,
|
||||
|
||||
// The last time a move event was fired
|
||||
lastMoveEventTime: 0,
|
||||
|
||||
// Whether to capture the next click event
|
||||
clickCapture: false
|
||||
};
|
||||
|
||||
var dndObserverCallback = (mutations) => {
|
||||
if (this._dnd.enabled !== this.dragAndDrop) {
|
||||
this._dnd.enabled = this.dragAndDrop;
|
||||
if (this._dnd.enabled) {
|
||||
this.addEventListener('touchstart', this);
|
||||
this.addEventListener('touchmove', this);
|
||||
this.addEventListener('touchcancel', this);
|
||||
this.addEventListener('touchend', this);
|
||||
this.addEventListener('mousedown', this);
|
||||
this.addEventListener('mousemove', this);
|
||||
this.addEventListener('mouseup', this);
|
||||
this.addEventListener('click', this, true);
|
||||
this.addEventListener('contextmenu', this, true);
|
||||
} else {
|
||||
this.cancelDrag();
|
||||
this.removeEventListener('touchstart', this);
|
||||
this.removeEventListener('touchmove', this);
|
||||
this.removeEventListener('touchcancel', this);
|
||||
this.removeEventListener('touchend', this);
|
||||
this.removeEventListener('mousedown', this);
|
||||
this.removeEventListener('mousemove', this);
|
||||
this.removeEventListener('mouseup', this);
|
||||
this.removeEventListener('click', this, true);
|
||||
this.removeEventListener('contextmenu', this, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.dndObserver = new MutationObserver(dndObserverCallback);
|
||||
this.dndObserver.observe(this,
|
||||
{ attributes: true, attributeFilter: ['drag-and-drop'] });
|
||||
dndObserverCallback(null);
|
||||
};
|
||||
|
||||
Object.defineProperty(proto, 'children', {
|
||||
get: function() {
|
||||
return this._children.map((child) => {
|
||||
return child.element;
|
||||
});
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(proto, 'firstChild', {
|
||||
get: function() {
|
||||
return this._children.length ? this._children[0].element : null;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(proto, 'lastChild', {
|
||||
get: function() {
|
||||
var length = this._children.length;
|
||||
return length ? this._children[length - 1].element : null;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(proto, 'dragAndDrop', {
|
||||
get: function() {
|
||||
return this.getAttribute('drag-and-drop') !== null;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(proto, 'dragAndDropTimeout', {
|
||||
get: function() {
|
||||
return this._dnd.delay;
|
||||
},
|
||||
set: function(timeout) {
|
||||
if (timeout >= 0) {
|
||||
this._dnd.delay = timeout;
|
||||
} else {
|
||||
this._dnd.delay = DEFAULT_DND_TIMEOUT;
|
||||
}
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
proto.realAppendChild = proto.appendChild;
|
||||
proto.appendChild = function(element, callback) {
|
||||
this.insertBefore(element, null, callback);
|
||||
};
|
||||
|
||||
proto.realRemoveChild = proto.removeChild;
|
||||
proto.removeChild = function(element, callback) {
|
||||
var children = this._children;
|
||||
var childToRemove = null;
|
||||
|
||||
for (var child of children) {
|
||||
if (child.element === element) {
|
||||
childToRemove = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (childToRemove === null) {
|
||||
throw 'removeChild called on unknown child';
|
||||
}
|
||||
|
||||
this.changeState(childToRemove, 'removed', () => {
|
||||
this.realRemoveChild(childToRemove.container);
|
||||
this.realRemoveChild(childToRemove.master);
|
||||
|
||||
// Find the child again. We need to do this in case the container was
|
||||
// manipulated between removing the child and this callback being reached.
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
if (children[i] === childToRemove) {
|
||||
children.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
this.synchronise();
|
||||
});
|
||||
};
|
||||
|
||||
proto.realReplaceChild = proto.replaceChild;
|
||||
proto.replaceChild = function(newElement, oldElement, callback) {
|
||||
if (!newElement || !oldElement) {
|
||||
throw 'replaceChild called with null arguments';
|
||||
}
|
||||
|
||||
// Unparent the newElement if necessary (with support for gaia-container)
|
||||
if (newElement.parentNode) {
|
||||
newElement.parentNode.removeChild(newElement, () => {
|
||||
this.replaceChild(newElement, oldElement, callback);
|
||||
});
|
||||
if (newElement.parentNode) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old child and add the new one, but don't run the removed/
|
||||
// added state changes.
|
||||
var children = this._children;
|
||||
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
var oldChild = children[i];
|
||||
if (oldChild.element === oldElement) {
|
||||
var newChild = new GaiaContainerChild(newElement);
|
||||
this.realInsertBefore(newChild.container, oldChild.container);
|
||||
this.realInsertBefore(newChild.master, oldChild.master);
|
||||
this.realRemoveChild(oldChild.container);
|
||||
this.realRemoveChild(oldChild.master);
|
||||
this.children.splice(i, 1, newChild);
|
||||
this.synchronise();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw 'removeChild called on unknown child';
|
||||
};
|
||||
|
||||
/**
|
||||
* Reorders the given element to appear before referenceElement.
|
||||
*/
|
||||
proto.reorderChild = function(element, referenceElement, callback) {
|
||||
if (!element) {
|
||||
throw 'reorderChild called with null element';
|
||||
}
|
||||
|
||||
var children = this._children;
|
||||
var child = null;
|
||||
var childIndex = null;
|
||||
var referenceChild = null;
|
||||
var referenceChildIndex = null;
|
||||
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
if (children[i].element === element) {
|
||||
child = children[i];
|
||||
childIndex = i;
|
||||
} else if (children[i].element === referenceElement) {
|
||||
referenceChild = children[i];
|
||||
referenceChildIndex = i;
|
||||
}
|
||||
|
||||
if (child && (referenceChild || !referenceElement)) {
|
||||
this.realRemoveChild(child.container);
|
||||
this.realRemoveChild(child.master);
|
||||
children.splice(childIndex, 1);
|
||||
|
||||
if (referenceChild) {
|
||||
this.realInsertBefore(child.container, referenceChild.container);
|
||||
this.realInsertBefore(child.master, referenceChild.master);
|
||||
} else {
|
||||
(children.length === 0) ?
|
||||
this.realAppendChild(child.container) :
|
||||
this.realInsertBefore(child.container, children[0].master);
|
||||
this.realAppendChild(child.master);
|
||||
}
|
||||
|
||||
referenceChild ?
|
||||
children.splice(
|
||||
referenceChildIndex - (childIndex < referenceChildIndex) ? 1 : 0,
|
||||
0, child) :
|
||||
children.splice(children.length, 0, child);
|
||||
|
||||
this.synchronise();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw child ? 'reorderChild called on unknown reference element' :
|
||||
'reorderChild called on unknown child';
|
||||
};
|
||||
|
||||
proto.realInsertBefore = proto.insertBefore;
|
||||
proto.insertBefore = function(element, reference, callback) {
|
||||
var children = this._children;
|
||||
var childToInsert = new GaiaContainerChild(element);
|
||||
var referenceIndex = -1;
|
||||
|
||||
if (reference !== null) {
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
if (children[i].element === reference) {
|
||||
referenceIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (referenceIndex === -1) {
|
||||
throw 'insertBefore called on unknown child';
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceIndex === -1) {
|
||||
(children.length === 0) ?
|
||||
this.realAppendChild(childToInsert.container) :
|
||||
this.realInsertBefore(childToInsert.container, children[0].master);
|
||||
this.realAppendChild(childToInsert.master);
|
||||
children.push(childToInsert);
|
||||
} else {
|
||||
this.realInsertBefore(childToInsert.container,
|
||||
children[referenceIndex].container);
|
||||
this.realInsertBefore(childToInsert.master,
|
||||
children[referenceIndex].master);
|
||||
children.splice(referenceIndex, 0, childToInsert);
|
||||
}
|
||||
|
||||
this.changeState(childToInsert, 'added', callback);
|
||||
this.synchronise();
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to execute a state-change of a child that may possibly be animated.
|
||||
* @state will be added to the child's class-list. If an animation starts that
|
||||
* has the same name, that animation will complete and @callback will be
|
||||
* called. Otherwise, the class will be removed and @callback called on the
|
||||
* next frame.
|
||||
*/
|
||||
proto.changeState = function(child, state, callback) {
|
||||
// Check that the child is still attached to this parent (can happen if
|
||||
// the child is removed while frozen).
|
||||
if (child.container.parentNode !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a redundant state change.
|
||||
if (child.container.classList.contains(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue up state change if we're frozen.
|
||||
if (this._frozen) {
|
||||
this._pendingStateChanges.push(
|
||||
this.changeState.bind(this, child, state, callback));
|
||||
return;
|
||||
}
|
||||
|
||||
var animStart = (e) => {
|
||||
if (!e.animationName.endsWith(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.container.removeEventListener('animationstart', animStart);
|
||||
|
||||
window.clearTimeout(child[state]);
|
||||
delete child[state];
|
||||
|
||||
var self = this;
|
||||
child.container.addEventListener('animationend', function animEnd() {
|
||||
child.container.removeEventListener('animationend', animEnd);
|
||||
child.container.classList.remove(state);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
child.container.addEventListener('animationstart', animStart);
|
||||
child.container.classList.add(state);
|
||||
|
||||
child[state] = window.setTimeout(() => {
|
||||
delete child[state];
|
||||
child.container.removeEventListener('animationstart', animStart);
|
||||
child.container.classList.remove(state);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}, STATE_CHANGE_TIMEOUT);
|
||||
};
|
||||
|
||||
proto.getChildOffsetRect = function(element) {
|
||||
var children = this._children;
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
var child = children[i];
|
||||
if (child.element === element) {
|
||||
var top = child._lastMasterTop;
|
||||
var left = child._lastMasterLeft;
|
||||
var width = child._lastElementWidth;
|
||||
var height = child._lastElementHeight;
|
||||
|
||||
return {
|
||||
top: top,
|
||||
left: left,
|
||||
width: width,
|
||||
height: height,
|
||||
right: left + width,
|
||||
bottom: top + height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw 'getChildOffsetRect called on unknown child';
|
||||
};
|
||||
|
||||
proto.getChildFromPoint = function(x, y) {
|
||||
var children = this._children;
|
||||
for (var parent = this.parentElement; parent;
|
||||
parent = parent.parentElement) {
|
||||
x += parent.scrollLeft - parent.offsetLeft;
|
||||
y += parent.scrollTop - parent.offsetTop;
|
||||
}
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
var child = children[i];
|
||||
if (x >= child._lastMasterLeft &&
|
||||
y >= child._lastMasterTop &&
|
||||
x < child._lastMasterLeft + child._lastElementWidth &&
|
||||
y < child._lastMasterTop + child._lastElementHeight) {
|
||||
return child.element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
proto.cancelDrag = function() {
|
||||
if (this._dnd.timeout !== null) {
|
||||
clearTimeout(this._dnd.timeout);
|
||||
this._dnd.timeout = null;
|
||||
}
|
||||
|
||||
if (this._dnd.moveTimeout !== null) {
|
||||
clearTimeout(this._dnd.moveTimeout);
|
||||
this._dnd.moveTimeout = null;
|
||||
}
|
||||
|
||||
if (this._dnd.active) {
|
||||
this._dnd.child.container.classList.remove('dragging');
|
||||
this._dnd.child.container.style.position = 'absolute';
|
||||
this._dnd.child.container.style.top = '0';
|
||||
this._dnd.child.container.style.left = '0';
|
||||
this._dnd.child.markDirty();
|
||||
this._dnd.child = null;
|
||||
this._dnd.active = false;
|
||||
this.synchronise();
|
||||
this._dnd.clickCapture = true;
|
||||
this.dispatchEvent(new CustomEvent('drag-finish'));
|
||||
}
|
||||
};
|
||||
|
||||
proto.startDrag = function() {
|
||||
if (!this.dispatchEvent(new CustomEvent('drag-start',
|
||||
{ cancelable: true,
|
||||
detail: { target: this._dnd.child.element,
|
||||
pageX: this._dnd.start.pageX,
|
||||
pageY: this._dnd.start.pageY,
|
||||
clientX: this._dnd.start.clientX,
|
||||
clientY: this._dnd.start.clientY } }))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dnd.active = true;
|
||||
this._dnd.child.container.classList.add('dragging');
|
||||
this._dnd.child.container.style.position = 'fixed';
|
||||
var rect = this.getBoundingClientRect();
|
||||
this._dnd.child.container.style.top = rect.top + 'px';
|
||||
this._dnd.child.container.style.left = rect.left + 'px';
|
||||
};
|
||||
|
||||
proto.continueDrag = function() {
|
||||
if (!this._dnd.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
var left = this._dnd.child.master.offsetLeft +
|
||||
(this._dnd.last.pageX - this._dnd.start.pageX);
|
||||
var top = this._dnd.child.master.offsetTop +
|
||||
(this._dnd.last.pageY - this._dnd.start.pageY);
|
||||
this._dnd.child.container.style.transform =
|
||||
'translate(' + left + 'px, ' + top + 'px)';
|
||||
|
||||
if (this._dnd.moveTimeout === null) {
|
||||
var delay = Math.max(0, DND_MOVE_THROTTLE -
|
||||
(this._dnd.last.timeStamp - this._dnd.lastMoveEventTime));
|
||||
this._dnd.moveTimeout = setTimeout(() => {
|
||||
this._dnd.moveTimeout = null;
|
||||
this._dnd.lastMoveEventTime = this._dnd.last.timeStamp;
|
||||
this.dispatchEvent(new CustomEvent('drag-move',
|
||||
{ detail: { target: this._dnd.child.element,
|
||||
pageX: this._dnd.last.pageX,
|
||||
pageY: this._dnd.last.pageY,
|
||||
clientX: this._dnd.last.clientX,
|
||||
clientY: this._dnd.last.clientY } }));
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
proto.endDrag = function(event) {
|
||||
if (this._dnd.active) {
|
||||
var dropTarget = this.getChildFromPoint(this._dnd.last.clientX,
|
||||
this._dnd.last.clientY);
|
||||
|
||||
if (this.dispatchEvent(new CustomEvent('drag-end',
|
||||
{ cancelable: true,
|
||||
detail: { target: this._dnd.child.element,
|
||||
dropTarget: dropTarget,
|
||||
pageX: this._dnd.last.pageX,
|
||||
pageY: this._dnd.last.pageY,
|
||||
clientX: this._dnd.last.clientX,
|
||||
clientY: this._dnd.last.clientY }}))) {
|
||||
var children = this._children;
|
||||
|
||||
if (dropTarget && dropTarget !== this._dnd.child.element) {
|
||||
var dropChild = null;
|
||||
var dropIndex = -1;
|
||||
var childIndex = -1;
|
||||
var insertBefore = true;
|
||||
for (var i = 0, iLen = children.length; i < iLen; i++) {
|
||||
if (children[i] === this._dnd.child) {
|
||||
childIndex = i;
|
||||
if (!dropChild) {
|
||||
insertBefore = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (children[i].element === dropTarget) {
|
||||
dropChild = children[i];
|
||||
dropIndex = i;
|
||||
}
|
||||
|
||||
if (dropIndex >= 0 && childIndex >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dropIndex >= 0 && childIndex >= 0) {
|
||||
// Default action, rearrange the dragged child to before or after
|
||||
// the child underneath the touch/mouse point.
|
||||
this.realRemoveChild(this._dnd.child.container);
|
||||
this.realRemoveChild(this._dnd.child.master);
|
||||
this.realInsertBefore(this._dnd.child.container,
|
||||
insertBefore ? dropChild.container :
|
||||
dropChild.container.nextSibling);
|
||||
this.realInsertBefore(this._dnd.child.master,
|
||||
insertBefore ? dropChild.master :
|
||||
dropChild.master.nextSibling);
|
||||
children.splice(dropIndex, 0,
|
||||
children.splice(childIndex, 1)[0]);
|
||||
this.dispatchEvent(new CustomEvent('drag-rearrange'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this._dnd.timeout !== null) {
|
||||
var handled = !this.dispatchEvent(new CustomEvent('activate',
|
||||
{ cancelable : true, detail: { target: this._dnd.child.element } }));
|
||||
if (handled) {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
this.cancelDrag();
|
||||
};
|
||||
|
||||
proto.handleEvent = function(event) {
|
||||
switch (event.type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
if (this._dnd.active || this._dnd.timeout) {
|
||||
this.cancelDrag();
|
||||
break;
|
||||
}
|
||||
|
||||
if (event instanceof MouseEvent) {
|
||||
this._dnd.start.pageX = event.pageX;
|
||||
this._dnd.start.pageY = event.pageY;
|
||||
this._dnd.start.clientX = event.pageX;
|
||||
this._dnd.start.clientY = event.pageY;
|
||||
} else {
|
||||
this._dnd.start.pageX = event.touches[0].pageX;
|
||||
this._dnd.start.pageY = event.touches[0].pageY;
|
||||
this._dnd.start.clientX = event.touches[0].clientX;
|
||||
this._dnd.start.clientY = event.touches[0].clientY;
|
||||
}
|
||||
|
||||
this._dnd.last.pageX = this._dnd.start.pageX;
|
||||
this._dnd.last.pageY = this._dnd.start.pageY;
|
||||
this._dnd.last.clientX = this._dnd.start.clientX;
|
||||
this._dnd.last.clientY = this._dnd.start.clientY;
|
||||
this._dnd.last.timeStamp = event.timeStamp;
|
||||
|
||||
var target = event.target;
|
||||
for (; target.parentNode !== this; target = target.parentNode) {
|
||||
if (target === this || !target.parentNode) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the child
|
||||
var children = this._children;
|
||||
for (var child of children) {
|
||||
if (child.container === target) {
|
||||
this._dnd.child = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._dnd.child) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._dnd.delay > 0) {
|
||||
this._dnd.timeout = setTimeout(() => {
|
||||
this._dnd.timeout = null;
|
||||
this.startDrag();
|
||||
}, this._dnd.delay);
|
||||
} else {
|
||||
this.startDrag();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
var pageX, pageY, clientX, clientY;
|
||||
if (event instanceof MouseEvent) {
|
||||
pageX = event.pageX;
|
||||
pageY = event.pageY;
|
||||
clientX = event.clientX;
|
||||
clientY = event.clientY;
|
||||
} else {
|
||||
pageX = event.touches[0].pageX;
|
||||
pageY = event.touches[0].pageY;
|
||||
clientX = event.touches[0].clientX;
|
||||
clientY = event.touches[0].clientY;
|
||||
}
|
||||
|
||||
if (this._dnd.timeout) {
|
||||
if (Math.abs(pageX - this._dnd.start.pageX) > DND_THRESHOLD ||
|
||||
Math.abs(pageY - this._dnd.start.pageY) > DND_THRESHOLD) {
|
||||
clearTimeout(this._dnd.timeout);
|
||||
this._dnd.timeout = null;
|
||||
}
|
||||
} else if (this._dnd.active) {
|
||||
event.preventDefault();
|
||||
this._dnd.last.pageX = pageX;
|
||||
this._dnd.last.pageY = pageY;
|
||||
this._dnd.last.clientX = clientX;
|
||||
this._dnd.last.clientY = clientY;
|
||||
this._dnd.last.timeStamp = event.timeStamp;
|
||||
this.continueDrag();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'touchcancel':
|
||||
this.cancelDrag();
|
||||
break;
|
||||
|
||||
case 'touchend':
|
||||
case 'mouseup':
|
||||
if (this._dnd.active) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
this.endDrag(event);
|
||||
break;
|
||||
|
||||
case 'click':
|
||||
if (this._dnd.clickCapture) {
|
||||
this._dnd.clickCapture = false;
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contextmenu':
|
||||
if (this._dnd.active || this._dnd.timeout) {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporarily disables element position synchronisation. Useful when adding
|
||||
* multiple elements to the container at the same time, or in quick
|
||||
* succession.
|
||||
*/
|
||||
proto.freeze = function() {
|
||||
this._frozen = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables element position synchronisation after freeze() has been called.
|
||||
*/
|
||||
proto.thaw = function() {
|
||||
if (this._frozen) {
|
||||
this._frozen = false;
|
||||
for (var callback of this._pendingStateChanges) {
|
||||
callback();
|
||||
}
|
||||
this._pendingStateChanges = [];
|
||||
this.synchronise();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronise positions between the managed container and all children.
|
||||
* This is called automatically when adding/inserting or removing children,
|
||||
* but must be called manually if the managed container is manipulated
|
||||
* outside of these methods (for example, if style properties change, or
|
||||
* if it's resized).
|
||||
*/
|
||||
proto.synchronise = function() {
|
||||
if (this._frozen) {
|
||||
return;
|
||||
}
|
||||
|
||||
var child;
|
||||
for (child of this._children) {
|
||||
if (!this._dnd.active || child !== this._dnd.child) {
|
||||
child.synchroniseMaster();
|
||||
}
|
||||
}
|
||||
|
||||
for (child of this._children) {
|
||||
if (!this._dnd.active || child !== this._dnd.child) {
|
||||
child.synchroniseContainer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var template = document.createElement('template');
|
||||
|
||||
template.innerHTML =
|
||||
`<style> :host { position: relative; display: block; }</style>` +
|
||||
`<content select='*'></content>`;
|
||||
|
||||
function GaiaContainerChild(element) {
|
||||
this._element = element;
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
GaiaContainerChild.prototype = {
|
||||
get element() {
|
||||
return this._element;
|
||||
},
|
||||
|
||||
/**
|
||||
* The element that will contain the child element and control its position.
|
||||
*/
|
||||
get container() {
|
||||
if (!this._container) {
|
||||
// Create container
|
||||
var container = document.createElement('div');
|
||||
container.classList.add('gaia-container-child');
|
||||
container.style.position = 'absolute';
|
||||
container.style.top = '0';
|
||||
container.style.left = '0';
|
||||
container.appendChild(this.element);
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
return this._container;
|
||||
},
|
||||
|
||||
/**
|
||||
* The element that will be added to the container that will
|
||||
* control the element's transform.
|
||||
*/
|
||||
get master() {
|
||||
if (!this._master) {
|
||||
// Create master
|
||||
var master = document.createElement('div');
|
||||
master.style.visibility = 'hidden';
|
||||
this._master = master;
|
||||
}
|
||||
|
||||
return this._master;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears any cached style properties. To be used if elements are
|
||||
* manipulated outside of the methods of this object.
|
||||
*/
|
||||
markDirty() {
|
||||
this._lastElementWidth = null;
|
||||
this._lastElementHeight = null;
|
||||
this._lastElementDisplay = null;
|
||||
this._lastElementOrder = null;
|
||||
this._lastMasterTop = null;
|
||||
this._lastMasterLeft = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronise the size of the master with the managed child element.
|
||||
*/
|
||||
synchroniseMaster() {
|
||||
var master = this.master;
|
||||
var element = this.element;
|
||||
|
||||
var style = window.getComputedStyle(element);
|
||||
var display = style.display;
|
||||
var order = style.order;
|
||||
var width = element.offsetWidth;
|
||||
var height = element.offsetHeight;
|
||||
if (this._lastElementWidth !== width ||
|
||||
this._lastElementHeight !== height ||
|
||||
this._lastElementDisplay !== display ||
|
||||
this._lastElementOrder !== order) {
|
||||
this._lastElementWidth = width;
|
||||
this._lastElementHeight = height;
|
||||
this._lastElementDisplay = display;
|
||||
this._lastElementOrder = order;
|
||||
|
||||
master.style.width = width + 'px';
|
||||
master.style.height = height + 'px';
|
||||
master.style.display = display;
|
||||
master.style.order = order;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronise the container's transform with the position of the master.
|
||||
*/
|
||||
synchroniseContainer() {
|
||||
var master = this.master;
|
||||
var container = this.container;
|
||||
|
||||
var top = master.offsetTop;
|
||||
var left = master.offsetLeft;
|
||||
|
||||
if (this._lastMasterTop !== top ||
|
||||
this._lastMasterLeft !== left) {
|
||||
this._lastMasterTop = top;
|
||||
this._lastMasterLeft = left;
|
||||
|
||||
container.style.transform = 'translate(' + left + 'px, ' + top + 'px)';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.GaiaContainerChild = GaiaContainerChild;
|
||||
|
||||
return document.registerElement('gaia-container', { prototype: proto });
|
||||
})(window);
|
||||
28
gaia/apps/homescreen/bower_components/gaia-container/test/karma.conf.js
vendored
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
/* global module */
|
||||
// Karma configuration
|
||||
'use strict';
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
frameworks: ['mocha', 'sinon-chai'],
|
||||
client: {
|
||||
captureConsole: true,
|
||||
mocha: {'ui': 'tdd'}
|
||||
},
|
||||
|
||||
basePath: '../',
|
||||
files: [
|
||||
'script.js',
|
||||
|
||||
'test/test.js'
|
||||
],
|
||||
|
||||
browsers: ['FirefoxLatest'],
|
||||
customLaunchers: {
|
||||
FirefoxLatest: {
|
||||
base: 'FirefoxNightly',
|
||||
prefs: {'dom.webcomponents.enabled': true}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
194
gaia/apps/homescreen/bower_components/gaia-container/test/test.js
vendored
Executable file
|
|
@ -0,0 +1,194 @@
|
|||
/* global sinon, assert, suite, setup, teardown, test */
|
||||
'use strict';
|
||||
|
||||
suite('GaiaContainer', () => {
|
||||
var sinonSandbox, dom, el;
|
||||
|
||||
setup(() => {
|
||||
sinonSandbox = sinon.sandbox.create();
|
||||
// DOM container to put test cases
|
||||
dom = document.createElement('div');
|
||||
dom.innerHTML = '<gaia-container></gaia-container>';
|
||||
el = dom.firstElementChild;
|
||||
document.body.appendChild(dom);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sinonSandbox.restore();
|
||||
document.body.removeChild(dom);
|
||||
dom = null;
|
||||
});
|
||||
|
||||
suite('Drag and drop feature', () => {
|
||||
test('drag-and-drop attribute should not be set by default', () => {
|
||||
assert.equal(el.getAttribute('drag-and-drop'), null);
|
||||
assert.equal(el.hasAttribute('drag-and-drop'), false);
|
||||
assert.equal(el.dragAndDrop, false);
|
||||
});
|
||||
|
||||
test('.dragAndDrop property value should change with the attribute', () => {
|
||||
dom.innerHTML = '<gaia-container drag-and-drop></gaia-container>';
|
||||
el = dom.firstElementChild;
|
||||
assert.equal(el.getAttribute('drag-and-drop'), '');
|
||||
assert.equal(el.hasAttribute('drag-and-drop'), true);
|
||||
assert.equal(el.dragAndDrop, true);
|
||||
|
||||
el.removeAttribute('drag-and-drop');
|
||||
assert.equal(el.getAttribute('drag-and-drop'), null);
|
||||
assert.equal(el.hasAttribute('drag-and-drop'), false);
|
||||
assert.equal(el.dragAndDrop, false);
|
||||
});
|
||||
|
||||
test('.dragAndDropTimeout should only accept positive value', () => {
|
||||
var otherValue = 9999;
|
||||
assert.equal(typeof el.dragAndDropTimeout, 'number');
|
||||
|
||||
el.dragAndDropTimeout = otherValue;
|
||||
assert.equal(el.dragAndDropTimeout, otherValue);
|
||||
|
||||
el.dragAndDropTimeout = 0;
|
||||
el.dragAndDropTimeout = -5555;
|
||||
assert.equal(el.dragAndDropTimeout, otherValue);
|
||||
});
|
||||
|
||||
suite('Events', () => {
|
||||
var icon;
|
||||
setup(() => {
|
||||
icon = document.createElement('div');
|
||||
el.appendChild(icon);
|
||||
el.dragAndDropTimeout = 1;
|
||||
});
|
||||
|
||||
test('drag-start', done => {
|
||||
el.handleEvent({
|
||||
type: 'touchstart',
|
||||
target: icon,
|
||||
touches: [{
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
clientX: 0,
|
||||
clientY: 0
|
||||
}]
|
||||
});
|
||||
|
||||
el.addEventListener('drag-start', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('drag-start resets last-drag', done => {
|
||||
el._dnd.last.pageX = el._dnd.last.pageY =
|
||||
el._dnd.last.clientX = el._dnd.last.clientY =
|
||||
el._dnd.last.timeStamp = 100;
|
||||
|
||||
el.handleEvent({
|
||||
type: 'touchstart',
|
||||
target: icon,
|
||||
timeStamp: 0,
|
||||
touches: [{
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
clientX: 0,
|
||||
clientY: 0
|
||||
}]
|
||||
});
|
||||
|
||||
el.addEventListener('drag-start', () => {
|
||||
assert.equal(el._dnd.last.pageX, 0);
|
||||
assert.equal(el._dnd.last.pageY, 0);
|
||||
assert.equal(el._dnd.last.clientX, 0);
|
||||
assert.equal(el._dnd.last.clientY, 0);
|
||||
assert.equal(el._dnd.last.timeStamp, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('DOM manipulation', () => {
|
||||
var nodeA, nodeB;
|
||||
|
||||
setup(() => {
|
||||
nodeA = document.createElement('div');
|
||||
nodeB = document.createElement('div');
|
||||
|
||||
el.appendChild(nodeA);
|
||||
});
|
||||
|
||||
test('should add and remove child nodes', done => {
|
||||
assert.equal(el.children.length, 1);
|
||||
|
||||
el.appendChild(nodeB);
|
||||
|
||||
assert.equal(el.children.length, 2);
|
||||
assert.equal(el.firstChild, nodeA);
|
||||
assert.equal(el.lastChild, nodeB);
|
||||
|
||||
el.removeChild(nodeB, () => {
|
||||
assert.equal(el.children.length, 1);
|
||||
assert.equal(el.firstChild, nodeA);
|
||||
assert.equal(el.lastChild, nodeA);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('.replaceChild()', done => {
|
||||
el.appendChild(nodeB);
|
||||
|
||||
el.replaceChild(nodeB, nodeA, () => {
|
||||
assert.equal(el.children.length, 2);
|
||||
assert.equal(el.firstChild, nodeA);
|
||||
assert.equal(el.lastChild, nodeB);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('.insertBefore()', done => {
|
||||
el.insertBefore(nodeB, nodeA, () => {
|
||||
assert.equal(el.children.length, 2);
|
||||
assert.equal(el.firstChild, nodeB);
|
||||
assert.equal(el.lastChild, nodeA);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('.reorderChild()', done => {
|
||||
el.appendChild(nodeB);
|
||||
|
||||
el.reorderChild(nodeB, nodeA, () => {
|
||||
assert.equal(el.children.length, 2);
|
||||
assert.equal(el.firstChild, nodeB);
|
||||
assert.equal(el.lastChild, nodeA);
|
||||
|
||||
el.reorderChild(nodeB, null, () => {
|
||||
assert.equal(el.children.length, 2);
|
||||
assert.equal(el.firstChild, nodeA);
|
||||
assert.equal(el.lastChild, nodeB);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Utility functions', () => {
|
||||
test('.getBoundingClientRect()', done => {
|
||||
var nodeA = document.createElement('div');
|
||||
nodeA.style.width = '200px';
|
||||
nodeA.style.height = '100px';
|
||||
|
||||
el.style.paddingLeft = '100px';
|
||||
el.style.paddingTop = '50px';
|
||||
|
||||
el.appendChild(nodeA, () => {
|
||||
var rect = el.getChildOffsetRect(nodeA);
|
||||
assert.equal(rect.width, 200);
|
||||
assert.equal(rect.height, 100);
|
||||
assert.equal(rect.left, 100);
|
||||
assert.equal(rect.top, 50);
|
||||
assert.equal(rect.right, 300);
|
||||
assert.equal(rect.bottom, 150);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
40
gaia/apps/homescreen/bower_components/gaia-dialog/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "gaia-dialog",
|
||||
"homepage": "https://github.com/gaia-components/gaia-dialog",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-dialog.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"base": "gaia-components/base",
|
||||
"pressed": "gaia-components/pressed",
|
||||
"gaia-fonts": "gaia-components/gaia-fonts",
|
||||
"gaia-theme": "gaia-components/gaia-theme",
|
||||
"gaia-button": "gaia-components/gaia-button"
|
||||
},
|
||||
"dependencies": {
|
||||
"gaia-text-input": "gaia-components/gaia-text-input",
|
||||
"gaia-icons": "gaia-components/gaia-icons#^0.10.2",
|
||||
"gaia-component": "gaia-components/gaia-component#^0.3.9"
|
||||
},
|
||||
"_release": "0bf523d279",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "0bf523d2791cea3649ac36983699cb5ee410a19a"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/gaia-dialog.git",
|
||||
"_target": "master",
|
||||
"_originalSource": "gaia-components/gaia-dialog"
|
||||
}
|
||||
32
gaia/apps/homescreen/bower_components/gaia-dialog/bower.json
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "gaia-dialog",
|
||||
"version": "0.2.3",
|
||||
"homepage": "https://github.com/gaia-components/gaia-dialog",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-dialog.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"base": "gaia-components/base",
|
||||
"pressed": "gaia-components/pressed",
|
||||
"gaia-fonts": "gaia-components/gaia-fonts",
|
||||
"gaia-theme": "gaia-components/gaia-theme",
|
||||
"gaia-button": "gaia-components/gaia-button"
|
||||
},
|
||||
"dependencies": {
|
||||
"gaia-text-input": "gaia-components/gaia-text-input",
|
||||
"gaia-icons": "gaia-components/gaia-icons#^0.10.2",
|
||||
"gaia-component": "gaia-components/gaia-component#^0.3.9"
|
||||
}
|
||||
}
|
||||
115
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-action.js
vendored
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-action', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
|
||||
this.els.dialog.addEventListener('closed',
|
||||
GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
// First close (hide) inner dialog and then the container.
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<content select="h1"></content>
|
||||
</section>
|
||||
<content select="button"></content>
|
||||
<button on-click="close" class="cancel">Cancel</button>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content section {
|
||||
padding: 33px 16px;
|
||||
}
|
||||
|
||||
::content button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 25px;
|
||||
font: inherit;
|
||||
background: var(--color-beta);
|
||||
color: var(--color-epsilon);
|
||||
transition: all 200ms;
|
||||
transition-delay: 300ms;
|
||||
}
|
||||
|
||||
::content button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content button:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::content button:active {
|
||||
background-color: var(--highlight-color);
|
||||
color: #fff;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button:active:after {
|
||||
background: var(--highlight-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-action',this));
|
||||
59
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-alert.js
vendored
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-alert', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog')
|
||||
};
|
||||
this.els.dialog.addEventListener('closed',
|
||||
GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<p><content></content></p>
|
||||
</section>
|
||||
<div>
|
||||
<button class="submit primary" on-click="close">Ok</button>
|
||||
</div>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-alert',this));
|
||||
70
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-confirm.js
vendored
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-confirm', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
// First close (hide) inner dialog and then the container.
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<p><content></content></p>
|
||||
</section>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit danger">Confirm</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-confirm',this));
|
||||
104
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-menu.js
vendored
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-menu', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
items: this.shadowRoot.querySelector('.items')
|
||||
};
|
||||
|
||||
this.els.dialog.addEventListener('closed',
|
||||
GaiaDialogProto.hide.bind(this));
|
||||
|
||||
setTimeout(() => this.makeAccessible());
|
||||
},
|
||||
|
||||
makeAccessible() {
|
||||
this.els.items.setAttribute('role', 'menu');
|
||||
[].forEach.call(this.querySelectorAll('button'),
|
||||
menuitem => menuitem.setAttribute('role', 'menuitem'));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<div class="items"><content select="button"></content></div>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::content > button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 51px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 16px;
|
||||
font: inherit;
|
||||
font-style: italic;
|
||||
text-align: start;
|
||||
background: var(--color-beta);
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
|
||||
::content > button[data-icon]:before {
|
||||
width: 50px;
|
||||
font-size: 22px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/** Button Divider Line
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content > button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content > button:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-menu',this));
|
||||
83
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-prompt.js
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
require('gaia-text-input');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-prompt', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
input: this.shadowRoot.querySelector('gaia-text-input'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
|
||||
this.els.dialog.addEventListener('opened', () => {
|
||||
this.setAttribute('opened', '');
|
||||
});
|
||||
|
||||
this.els.dialog.addEventListener('closed', () => {
|
||||
this.removeAttribute('opened');
|
||||
});
|
||||
|
||||
this.els.input.placeholder = this.firstChild.textContent;
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<div><gaia-text-input></gaia-text-input></div>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit primary">Ok</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
gaia-text-input {
|
||||
margin: 16px !important;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-prompt',this));
|
||||
247
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog-select.js
vendored
Executable file
|
|
@ -0,0 +1,247 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-select', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel'),
|
||||
list: this.shadowRoot.querySelector('ul')
|
||||
};
|
||||
|
||||
this.multiple = this.hasAttribute('multiple');
|
||||
this.els.list.addEventListener('click', this.onListClick.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
|
||||
setTimeout(() => this.makeAccessible());
|
||||
},
|
||||
|
||||
makeAccessible() {
|
||||
this.els.list.setAttribute('role', 'listbox');
|
||||
if (this.multiple) {
|
||||
this.els.list.setAttribute('aria-multiselectable', true);
|
||||
}
|
||||
|
||||
[].forEach.call(this.options, option => {
|
||||
option.setAttribute('role', 'option');
|
||||
option.setAttribute('tabindex', '0');
|
||||
});
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
onListClick: function(e) {
|
||||
var el = e.target.closest('li');
|
||||
var selected = el.getAttribute('aria-selected') === 'true';
|
||||
|
||||
if (this.multiple) { this.onChangeMultiple(el, selected); }
|
||||
else { this.onChange(el, selected); }
|
||||
},
|
||||
|
||||
onChange: function(el, selected) {
|
||||
this.clearSelected();
|
||||
if (!selected) { el.setAttribute('aria-selected', !selected); }
|
||||
this.fireChange();
|
||||
this.close();
|
||||
},
|
||||
|
||||
onChangeMultiple: function(el, selected) {
|
||||
el.setAttribute('aria-selected', !selected);
|
||||
this.fireChange();
|
||||
},
|
||||
|
||||
clearSelected: function() {
|
||||
[].forEach.call(this.selectedOptions, function(option) {
|
||||
option.removeAttribute('aria-selected');
|
||||
});
|
||||
},
|
||||
|
||||
fireChange: function() {
|
||||
var e = new CustomEvent('change', { detail: { value: this.valueString }});
|
||||
this.dispatchEvent(e);
|
||||
},
|
||||
|
||||
attrs: {
|
||||
multiple: {
|
||||
get: function() { return !!this._multiple; },
|
||||
set: function(value) {
|
||||
value = value === '' ? true : value;
|
||||
if (value === this._multiple) { return; }
|
||||
if (!value) {
|
||||
this._multiple = false;
|
||||
this.removeAttribute('multiple');
|
||||
this.els.list.removeAttribute('aria-multiselectable');
|
||||
} else {
|
||||
this._multiple = true;
|
||||
this.setAttribute('multiple', '');
|
||||
this.els.list.setAttribute('aria-multiselectable', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
options: {
|
||||
get: function() { return this.querySelectorAll('li'); }
|
||||
},
|
||||
|
||||
selectedOptions: {
|
||||
get: function() {
|
||||
return this.querySelectorAll('li[aria-selected="true"]');
|
||||
}
|
||||
},
|
||||
|
||||
selected: {
|
||||
get: function() { return this.selectedOptions[0]; }
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() {
|
||||
var selected = this.selectedOptions[0];
|
||||
return selected && selected.getAttribute('value');
|
||||
}
|
||||
},
|
||||
|
||||
valueString: {
|
||||
get: function() {
|
||||
var selected = this.selected;
|
||||
return selected && selected.textContent;
|
||||
}
|
||||
},
|
||||
|
||||
length: {
|
||||
get: function() { return this.options.length; }
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<content select="h1"></content>
|
||||
<ul><content select="li"></content></ul>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit primary">Select</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Title (duplicate from gaia-dialog)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content h1 {
|
||||
padding: 16px;
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
/** List
|
||||
---------------------------------------------------------*/
|
||||
|
||||
ul {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Options
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content li {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 16px;
|
||||
text-align: start;
|
||||
-moz-user-select: none;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
::content li[aria-selected='true'] {
|
||||
color: #00CAF2;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
::content li:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 1px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content li:last-of-type:after {
|
||||
display: none
|
||||
}
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::host:not([multiple]) .submit {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::host:not([multiple]) .cancel {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
::host:not([multiple]) .cancel:after {
|
||||
display: none% !important;
|
||||
}
|
||||
|
||||
/** Tick Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content li[aria-selected='true']:before {
|
||||
content: 'tick';
|
||||
position: absolute;
|
||||
top: 50%; right: 20px;
|
||||
display: block;
|
||||
margin-top: -12px;
|
||||
font-family: 'gaia-icons';
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-select',this));
|
||||
483
gaia/apps/homescreen/bower_components/gaia-dialog/gaia-dialog.js
vendored
Executable file
|
|
@ -0,0 +1,483 @@
|
|||
/* global define */
|
||||
|
||||
;(function(define){'use strict';define((require,exports,module) => {
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Simple logger (toggle 0)
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
var debug = 0 ? console.log.bind(console) : () => {};
|
||||
|
||||
/**
|
||||
* Use the dom-scheduler if it's around,
|
||||
* else fallback to fake shim.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var schedule = (window.scheduler && typeof window.scheduler.mutation === "function" ? window.scheduler : null) || {
|
||||
mutation: block => Promise.resolve(block()),
|
||||
transition: (block, el, event, timeout) => {
|
||||
block();
|
||||
return after(el, event, timeout || 500);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-dialog', {
|
||||
created() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.dialog-inner'),
|
||||
background: this.shadowRoot.querySelector('.background'),
|
||||
window: this.shadowRoot.querySelector('.window')
|
||||
};
|
||||
|
||||
this.shadowRoot.addEventListener('click', e => this.onClick(e));
|
||||
|
||||
setTimeout(() => this.makeAccessible());
|
||||
},
|
||||
|
||||
makeAccessible() {
|
||||
this.setAttribute('role', 'dialog');
|
||||
},
|
||||
|
||||
onClick(e) {
|
||||
var el = e.target.closest('[on-click]');
|
||||
if (!el) { return; }
|
||||
debug('onClick');
|
||||
var method = el.getAttribute('on-click');
|
||||
if (typeof this[method] == 'function') { this[method](); }
|
||||
},
|
||||
|
||||
open(options) {
|
||||
if (this.isOpen) { return; }
|
||||
debug('open dialog');
|
||||
this.isOpen = true;
|
||||
|
||||
return this.show()
|
||||
.then(() => this.animateBackgroundIn(options))
|
||||
.then(() => this.animateWindowIn())
|
||||
.then(() => this.dispatch('opened'));
|
||||
},
|
||||
|
||||
close(options) {
|
||||
if (!this.isOpen) { return; }
|
||||
debug('close dialog');
|
||||
this.isOpen = false;
|
||||
|
||||
return this.animateWindowOut()
|
||||
.then(() => this.animateBackgroundOut())
|
||||
.then(() => this.hide())
|
||||
.then(() => this.dispatch('closed'));
|
||||
},
|
||||
|
||||
animateBackgroundIn(options) {
|
||||
if (options) { return this.animateBackgroundInFrom(options); }
|
||||
|
||||
var el = this.els.background;
|
||||
return schedule.transition(() => {
|
||||
debug('animate background in');
|
||||
el.classList.remove('animate-out');
|
||||
el.classList.add('animate-in');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
animateBackgroundOut() {
|
||||
var el = this.els.background;
|
||||
return schedule.transition(() => {
|
||||
debug('animate background out');
|
||||
el.classList.add('animate-out');
|
||||
el.classList.remove('animate-in');
|
||||
}, el, 'animationend')
|
||||
.then(() => el.style = '');
|
||||
},
|
||||
|
||||
animateBackgroundInFrom(pos) {
|
||||
var el = this.els.background;
|
||||
var scale = Math.sqrt(window.innerWidth * window.innerHeight) / 15;
|
||||
var duration = scale * 9;
|
||||
|
||||
return schedule.mutation(() => {
|
||||
el.classList.add('circular');
|
||||
el.classList.remove('animate-out');
|
||||
el.style.transform = `translate(${pos.clientX}px, ${pos.clientY}px)`;
|
||||
el.style.transitionDuration = duration + 'ms';
|
||||
el.offsetTop; // Hack, any ideas?
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
return schedule.transition(() => {
|
||||
debug('animate background in from', pos);
|
||||
el.style.transform += ` scale(${scale})`;
|
||||
el.style.opacity = 1;
|
||||
}, el, 'transitionend', duration * 1.5);
|
||||
});
|
||||
},
|
||||
|
||||
show() {
|
||||
return schedule.mutation(() => {
|
||||
debug('show');
|
||||
this.style.display = 'block';
|
||||
});
|
||||
},
|
||||
|
||||
hide() {
|
||||
return schedule.mutation(() => {
|
||||
debug('hide');
|
||||
this.style.display = 'none';
|
||||
});
|
||||
},
|
||||
|
||||
animateWindowIn() {
|
||||
var el = this.els.window;
|
||||
return schedule.transition(() => {
|
||||
debug('animate window in');
|
||||
el.classList.add('animate-in');
|
||||
el.classList.remove('animate-out');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
animateWindowOut() {
|
||||
var el = this.els.window;
|
||||
return schedule.transition(() => {
|
||||
debug('animate window out');
|
||||
el.classList.add('animate-out');
|
||||
el.classList.remove('animate-in');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
dispatch(name) {
|
||||
this.dispatchEvent(new CustomEvent(name));
|
||||
},
|
||||
|
||||
attrs: {
|
||||
opened: {
|
||||
get: function() { return !!this.isOpen; },
|
||||
set: function(value) {
|
||||
value = value === '' || value;
|
||||
if (!value) { this.close(); }
|
||||
else { this.open(); }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="dialog-inner">
|
||||
<div class="background" on-click="close"></div>
|
||||
<div class="window"><content></content></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
::content * {
|
||||
box-sizing: border-box;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
::content p,
|
||||
::content h1,
|
||||
::content h2,
|
||||
::content h3,
|
||||
::content h4,
|
||||
::content button,
|
||||
::content fieldset {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0px; left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 200;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.dialog-inner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/** Background
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: rgba(199,199,199,0.85);
|
||||
}
|
||||
|
||||
/**
|
||||
* .circular
|
||||
*/
|
||||
|
||||
.background.circular {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: -20px;
|
||||
border-radius: 50%;
|
||||
will-change: transform, opacity;
|
||||
transition-property: opacity, transform;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
/**
|
||||
* .animate-in
|
||||
*/
|
||||
|
||||
.background.animate-in {
|
||||
animation-name: gaia-dialog-fade-in;
|
||||
animation-duration: 260ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/**
|
||||
* .animate-out
|
||||
*/
|
||||
|
||||
.background.animate-out {
|
||||
animation-name: gaia-dialog-fade-out;
|
||||
animation-duration: 260ms;
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Window
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.window {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 350px;
|
||||
margin: auto;
|
||||
box-shadow: 0 1px 0 0px rgba(0,0,0,0.15);
|
||||
background: var(--color-iota);
|
||||
transition: opacity 300ms;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.window.animate-in {
|
||||
animation-name: gaia-dialog-entrance;
|
||||
animation-duration: 300ms;
|
||||
animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.window.animate-out {
|
||||
animation-name: gaia-dialog-fade-out;
|
||||
animation-duration: 150ms;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Title
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content h1 {
|
||||
padding: 16px;
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
::content strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
::content small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/** Section
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content section {
|
||||
padding: 33px 18px;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
::content section > *:not(:last-child) {
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
/** Paragraphs
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content p {
|
||||
text-align: -moz-start;
|
||||
}
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 16px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
background: var(--color-beta);
|
||||
color: var(--color-epsilon);
|
||||
transition: all 200ms;
|
||||
transition-delay: 300ms;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* .primary
|
||||
*/
|
||||
|
||||
::content button.primary {
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* .danger
|
||||
*/
|
||||
|
||||
::content button.danger {
|
||||
color: var(--color-destructive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled buttons
|
||||
*/
|
||||
|
||||
::content button[disabled] {
|
||||
color: var(--color-zeta);
|
||||
}
|
||||
|
||||
/** Button Divider Line
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content button:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::content button:active {
|
||||
background-color: var(--highlight-color);
|
||||
color: #fff;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button:active:after {
|
||||
background: var(--highlight-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button[data-icon]:before {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/** Fieldset (button group)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content fieldset {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::content fieldset button {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
::content fieldset button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
bottom: 6px;
|
||||
right: 0px;
|
||||
left: auto;
|
||||
width: 1px;
|
||||
height: calc(100% - 12px);
|
||||
background: #e7e7e7;
|
||||
transition: all 200ms;
|
||||
transition-delay: 200ms;
|
||||
}
|
||||
|
||||
</style>`,
|
||||
|
||||
globalCss: `
|
||||
@keyframes gaia-dialog-entrance {
|
||||
0% { transform: translateY(100px); }
|
||||
100% { transform: translateY(0px); }
|
||||
}
|
||||
|
||||
@keyframes gaia-dialog-fade-in {
|
||||
0% { opacity: 0 }
|
||||
100% { opacity: 1 }
|
||||
}
|
||||
|
||||
@keyframes gaia-dialog-fade-out {
|
||||
0% { opacity: 1 }
|
||||
100% { opacity: 0 }
|
||||
}`
|
||||
});
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function after(target, event, timeout) {
|
||||
return new Promise(resolve => {
|
||||
var timer = timeout && setTimeout(cb, timeout);
|
||||
target.addEventListener(event, cb);
|
||||
function cb() {
|
||||
target.removeEventListener(event, cb);
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog',this));
|
||||
39
gaia/apps/homescreen/bower_components/gaia-icons/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "gaia-icons",
|
||||
"version": "0.10.3",
|
||||
"homepage": "https://github.com/wilsonpage/gaia-icons",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": [
|
||||
"gaia-icons.js",
|
||||
"gaia-icons.css",
|
||||
"fonts/gaia-icons.ttf",
|
||||
"gaia-icons-embedded.css"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"templates",
|
||||
"images",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"create-svg-icon-set-file.py",
|
||||
"Gruntfile.js",
|
||||
"README.md",
|
||||
"package.json"
|
||||
],
|
||||
"_release": "0.10.3",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.10.3",
|
||||
"commit": "c3628ea7b1d0950dda833f86ccfd7d2782e5c8e3"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/gaia-icons.git",
|
||||
"_target": "^0.10.2",
|
||||
"_originalSource": "gaia-components/gaia-icons"
|
||||
}
|
||||
33
gaia/apps/homescreen/bower_components/gaia-icons/.jshintrc
vendored
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"camelcase": false,
|
||||
"curly": true,
|
||||
"forin": false,
|
||||
"latedef": "nofunc",
|
||||
"newcap": false,
|
||||
"noarg": true,
|
||||
"node": true,
|
||||
"nonew": true,
|
||||
"quotmark": "single",
|
||||
"undef": true,
|
||||
"unused": "vars",
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"maxlen": 80,
|
||||
|
||||
"eqnull": true,
|
||||
"esnext": true,
|
||||
"expr": true,
|
||||
"globalstrict": true,
|
||||
|
||||
"maxerr": 1000,
|
||||
"regexdash": true,
|
||||
"laxcomma": true,
|
||||
"proto": true,
|
||||
|
||||
"browser": true,
|
||||
"devel": true,
|
||||
"nonstandard": true,
|
||||
"worker": true,
|
||||
|
||||
"-W078": true
|
||||
}
|
||||
58
gaia/apps/homescreen/bower_components/gaia-icons/bidi-helper.css
vendored
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
/* Some BiDi-proof icons */
|
||||
|
||||
[data-icon="back"]:-moz-dir(ltr):before {
|
||||
content: 'left';
|
||||
}
|
||||
[data-icon="back"]:-moz-dir(rtl):before {
|
||||
content: 'right';
|
||||
}
|
||||
|
||||
[data-icon="forward"]:-moz-dir(ltr):before {
|
||||
content: 'right';
|
||||
}
|
||||
[data-icon="forward"]:-moz-dir(rtl):before {
|
||||
content: 'left';
|
||||
}
|
||||
|
||||
[data-icon="back-light"]:-moz-dir(ltr):before {
|
||||
content: 'left-light';
|
||||
}
|
||||
[data-icon="back-light"]:-moz-dir(rtl):before {
|
||||
content: 'right-light';
|
||||
}
|
||||
|
||||
[data-icon="forward-light"]:-moz-dir(ltr):before {
|
||||
content: 'right-light';
|
||||
}
|
||||
[data-icon="forward-light"]:-moz-dir(rtl):before {
|
||||
content: 'left-light';
|
||||
}
|
||||
|
||||
[data-icon="arrow-back"]:-moz-dir(ltr):before {
|
||||
content: 'arrow-left';
|
||||
}
|
||||
[data-icon="arrow-back"]:-moz-dir(rtl):before {
|
||||
content: 'arrow-right';
|
||||
}
|
||||
|
||||
[data-icon="arrow-forward"]:-moz-dir(ltr):before {
|
||||
content: 'arrow-right';
|
||||
}
|
||||
[data-icon="arrow-forward"]:-moz-dir(rtl):before {
|
||||
content: 'arrow-left';
|
||||
}
|
||||
|
||||
[data-icon="clear-input"]:-moz-dir(ltr):before {
|
||||
content: 'clear-input-left';
|
||||
}
|
||||
[data-icon="clear-input"]:-moz-dir(rtl):before {
|
||||
content: 'clear-input-right';
|
||||
}
|
||||
|
||||
[data-icon="expand"]:-moz-dir(ltr):before {
|
||||
content: 'expand-left';
|
||||
}
|
||||
[data-icon="expand"]:-moz-dir(rtl):before {
|
||||
content: 'expand-right';
|
||||
}
|
||||
|
||||
118
gaia/apps/homescreen/bower_components/gaia-icons/bin/report.js
vendored
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
'use strict';
|
||||
|
||||
// Create a report about unused icons in a project.
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var optimist = require('optimist');
|
||||
|
||||
var cli = optimist
|
||||
.usage('Create a report about unused icons in a project.' + '\n' +
|
||||
'Usage: $0 path/to/your/project/');
|
||||
var projectDir = cli.argv._[0];
|
||||
|
||||
// No folder passed? Show help message then.
|
||||
if (!projectDir) {
|
||||
cli.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check if the directory exist.
|
||||
var stats = fs.lstatSync(projectDir);
|
||||
if (!stats.isDirectory()) {
|
||||
console.log('\x1b[31mPlease provide a valid folder.\x1b[0m\n');
|
||||
cli.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all the icons in the font.
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getIcons() {
|
||||
var icons = fs.readdirSync('./images/');
|
||||
|
||||
icons = icons.map(function(icon) {
|
||||
return path.basename(icon, '.svg');
|
||||
});
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a grep pattern to extract used icons from files.
|
||||
* @param {string} grep
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function getUsedIcons(grep, callback) {
|
||||
exec(grep, function(err, stdout, stderr) {
|
||||
var usedIcons = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(function(icon) {
|
||||
// Take only the icon name.
|
||||
return icon.split(/["']/)[1];
|
||||
})
|
||||
.filter(function(icon) {
|
||||
// Remove non string items.
|
||||
return icon && icon.match(/^[a-z0-9-]+$/);
|
||||
});
|
||||
|
||||
callback(usedIcons);
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of unused icons in a folder. The patterns of extraction are:
|
||||
* * data-icon="[^"]+"
|
||||
* * .dataset.icon ?= ?.+
|
||||
*/
|
||||
function getUnusedIcons() {
|
||||
var usedIcons = [];
|
||||
var length = 2;
|
||||
|
||||
var cb = function(icons) {
|
||||
usedIcons = usedIcons.concat(icons);
|
||||
length--;
|
||||
|
||||
if (length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var allIcons = getIcons();
|
||||
var unusedIcons = [];
|
||||
|
||||
allIcons.forEach(function(icon) {
|
||||
if (usedIcons.indexOf(icon) === -1) {
|
||||
unusedIcons.push(icon);
|
||||
}
|
||||
});
|
||||
|
||||
outputUnusedIcons(allIcons, unusedIcons);
|
||||
};
|
||||
|
||||
getUsedIcons('grep -rhoP \'data-icon="[^"]+"\' ' + projectDir, cb);
|
||||
getUsedIcons('grep -rhoP \'.dataset.icon ?= ?.+\' ' + projectDir, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the list of unused icons to the screen.
|
||||
* @param {Array.<string>} icons
|
||||
* @param {Array.<string>} unusedIcons
|
||||
*/
|
||||
function outputUnusedIcons(icons, unusedIcons) {
|
||||
console.log('\x1b[36mList of unused icons in %s (%d/%d):\x1b[0m',
|
||||
projectDir, unusedIcons.length, icons.length);
|
||||
|
||||
unusedIcons.forEach(function(icon) {
|
||||
console.log('* ' + icon);
|
||||
});
|
||||
}
|
||||
|
||||
getUnusedIcons();
|
||||
30
gaia/apps/homescreen/bower_components/gaia-icons/bower.json
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "gaia-icons",
|
||||
"version": "0.10.3",
|
||||
"homepage": "https://github.com/wilsonpage/gaia-icons",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": [
|
||||
"gaia-icons.js",
|
||||
"gaia-icons.css",
|
||||
"fonts/gaia-icons.ttf",
|
||||
"gaia-icons-embedded.css"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"templates",
|
||||
"images",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"create-svg-icon-set-file.py",
|
||||
"Gruntfile.js",
|
||||
"README.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
BIN
gaia/apps/homescreen/bower_components/gaia-icons/fonts/gaia-icons.ttf
vendored
Executable file
41
gaia/apps/homescreen/bower_components/gaia-icons/gaia-icons-embedded.css
vendored
Executable file
41
gaia/apps/homescreen/bower_components/gaia-icons/gaia-icons.css
vendored
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
/* Generated by grunt-webfont */
|
||||
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "gaia-icons";
|
||||
src: url("fonts/gaia-icons.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
[data-icon]:before,
|
||||
.ligature-icons {
|
||||
font-family: "gaia-icons";
|
||||
content: attr(data-icon);
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-size: 30px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
|
||||
/* Highlight cases where icons are not localized and still visible to screen
|
||||
reader */
|
||||
background: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
width: 20rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
38
gaia/apps/homescreen/bower_components/gaia-icons/gaia-icons.js
vendored
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
/* global define */
|
||||
(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
var base = window.GAIA_ICONS_BASE_URL ||
|
||||
window.COMPONENTS_BASE_URL ||
|
||||
'bower_components/';
|
||||
|
||||
if (!document.documentElement) {
|
||||
window.addEventListener('load', load);
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (isLoaded()) { return; }
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = base + 'gaia-icons/gaia-icons.css';
|
||||
document.head.appendChild(link);
|
||||
exports.loaded = true;
|
||||
}
|
||||
|
||||
function isLoaded() {
|
||||
return exports.loaded ||
|
||||
document.querySelector('link[href*=gaia-icons]') ||
|
||||
document.documentElement.classList.contains('gaia-icons-loaded');
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-icons',this));
|
||||
37
gaia/apps/homescreen/bower_components/gaia-text-input/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "gaia-text-input",
|
||||
"version": "0.1.4",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-text-input.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"gaia-icons": "gaia-components/gaia-icons#~0.10.0",
|
||||
"gaia-component": "gaia-components/gaia-component#~0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gaia-fonts": "gaia-components/gaia-fonts#~0.2.1",
|
||||
"gaia-theme": "gaia-components/gaia-theme#~0.4.3",
|
||||
"base": "gaia-components/base",
|
||||
"gaia-icons": "gaia-components/gaia-icons#master"
|
||||
},
|
||||
"homepage": "https://github.com/gaia-components/gaia-text-input",
|
||||
"_release": "0.1.4",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.1.4",
|
||||
"commit": "d811482e143cc7990be04fdbfcc890f2a9fbd9d1"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/gaia-text-input.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "gaia-components/gaia-text-input"
|
||||
}
|
||||
27
gaia/apps/homescreen/bower_components/gaia-text-input/bower.json
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "gaia-text-input",
|
||||
"version": "0.1.4",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-text-input.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"gaia-icons": "gaia-components/gaia-icons#~0.10.0",
|
||||
"gaia-component": "gaia-components/gaia-component#~0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gaia-fonts": "gaia-components/gaia-fonts#~0.2.1",
|
||||
"gaia-theme": "gaia-components/gaia-theme#~0.4.3",
|
||||
"base": "gaia-components/base",
|
||||
"gaia-icons": "gaia-components/gaia-icons#master"
|
||||
}
|
||||
}
|
||||
216
gaia/apps/homescreen/bower_components/gaia-text-input/gaia-text-input-multiline.js
vendored
Executable file
|
|
@ -0,0 +1,216 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input-multiline', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
field: this.shadowRoot.querySelector('textarea')
|
||||
};
|
||||
|
||||
this.type = this.getAttribute('type');
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.placeholder = this.getAttribute('placeholder');
|
||||
this.required = this.getAttribute('required');
|
||||
this.value = this.getAttribute('value');
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
attrs: {
|
||||
placeholder: {
|
||||
get: function() { return this.els.field.placeholder; },
|
||||
set: function(value) { this.els.field.placeholder = value || ''; }
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.field.value; },
|
||||
set: function(value) { this.els.field.value = value; }
|
||||
},
|
||||
|
||||
required: {
|
||||
get: function() { return this.els.field.required; },
|
||||
set: function(value) { this.els.field.required = value; }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.field.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.field.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="fields">
|
||||
<textarea></textarea>
|
||||
<div class="focus focus-1"></div>
|
||||
<div class="focus focus-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
|
||||
/** Reset
|
||||
---------------------------------------------------------*/
|
||||
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Host
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] label {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
}
|
||||
|
||||
/** Textarea
|
||||
---------------------------------------------------------*/
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 86px;
|
||||
padding: 10px 16px;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
resize: none;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
color:
|
||||
var(--text-color, #000);
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disabled]
|
||||
*/
|
||||
|
||||
textarea[disabled] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/** Placeholder Text
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::-moz-placeholder {
|
||||
font-style: italic;
|
||||
|
||||
color:
|
||||
var(--input-placeholder-color, #909ca7);
|
||||
}
|
||||
|
||||
/** Focus Bar
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.focus {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
transition: all 200ms;
|
||||
transform: scaleX(0);
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
:focus ~ .focus {
|
||||
transform: scaleX(1);
|
||||
transition-delay: 200ms;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.focus-2 {
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input-multiline',this));
|
||||
279
gaia/apps/homescreen/bower_components/gaia-text-input/gaia-text-input-pin.js
vendored
Executable file
|
|
@ -0,0 +1,279 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
|
||||
var DEFAULT_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input-pin', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
fields: this.shadowRoot.querySelector('.fields'),
|
||||
input: this.shadowRoot.querySelector('input')
|
||||
};
|
||||
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.length = this.getAttribute('length') || DEFAULT_LENGTH;
|
||||
|
||||
this.addEventListener('keyup', () => this.updateCells());
|
||||
},
|
||||
|
||||
updateCells: function() {
|
||||
var l = this.els.input.value.length;
|
||||
this.els.cells.forEach((cell, i) => {
|
||||
cell.classList.toggle('populated', i < l);
|
||||
cell.classList.toggle('focused', i == l);
|
||||
});
|
||||
},
|
||||
|
||||
onBackspace: function(e) {
|
||||
var input = e.target;
|
||||
var empty = !input.value;
|
||||
var previous = input.previousElementSibling;
|
||||
|
||||
if (!empty && previous) {
|
||||
previous.clear();
|
||||
previous.focus();
|
||||
} else if (empty && previous) {
|
||||
previous.focus();
|
||||
}
|
||||
},
|
||||
|
||||
setupFields: function() {
|
||||
this.els.fields.innerHTML = '';
|
||||
this.els.cells = [];
|
||||
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'cell';
|
||||
this.els.fields.appendChild(el);
|
||||
this.els.cells.push(el);
|
||||
}
|
||||
},
|
||||
|
||||
attributeChangedCallback: function(attr, from, to) {
|
||||
if (attrs[attr]) { this[attr] = to; }
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
|
||||
attrs: {
|
||||
length: {
|
||||
get: function() { return this._length; },
|
||||
set: function(value) {
|
||||
value = Number(value);
|
||||
this._length = value;
|
||||
this.els.input.setAttribute('maxlength', this.length);
|
||||
this.setupFields();
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.input.value; },
|
||||
set: function(value) { this.els.input.value = value; }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.input.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.input.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="container">
|
||||
<input />
|
||||
<div class="fields"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
/** Host
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/** Container
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Input (hidden)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] + .fields {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/** Cell
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: 1rem;
|
||||
flex: 1;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] + .fields .cell {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/** Dot
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.cell::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: -7px;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
background:
|
||||
var(--text-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* .populated
|
||||
*/
|
||||
|
||||
.cell.populated::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cell::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
input:focus + .fields .cell::before {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
</style>
|
||||
`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input-pin',this));
|
||||
335
gaia/apps/homescreen/bower_components/gaia-text-input/gaia-text-input.js
vendored
Executable file
|
|
@ -0,0 +1,335 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
require('gaia-icons'); // Load gaia-icons
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input', {
|
||||
extends: HTMLInputElement,
|
||||
rtl: true,
|
||||
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
input: this.shadowRoot.querySelector('input'),
|
||||
clear: this.shadowRoot.querySelector('.clear')
|
||||
};
|
||||
|
||||
this.type = this.getAttribute('type');
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.clearable = this.hasAttribute('clearable');
|
||||
this.placeholder = this.getAttribute('placeholder');
|
||||
this.required = this.getAttribute('required');
|
||||
this.value = this.getAttribute('value');
|
||||
|
||||
// Don't take focus from the input field
|
||||
this.els.clear.addEventListener('mousedown', (e) => e.preventDefault());
|
||||
this.els.clear.addEventListener('click', e => this.clear(e));
|
||||
|
||||
// Set dir initially and listen for changes
|
||||
document.addEventListener('dirchanged', e => this.updateDir());
|
||||
this.updateDir();
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
focus: function() {
|
||||
this.els.input.focus();
|
||||
},
|
||||
|
||||
updateDir: function() {
|
||||
this.els.inner.setAttribute('dir', document.dir);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
|
||||
attrs: {
|
||||
type: {
|
||||
get: function() { return this.els.input.getAttribute('type'); },
|
||||
set: function(value) {
|
||||
if (!value) { return; }
|
||||
this.els.inner.setAttribute('type', value);
|
||||
this.els.input.setAttribute('type', value);
|
||||
}
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
get: function() { return this.field.placeholder; },
|
||||
set: function(value) {
|
||||
if (!value && value !== '') { return; }
|
||||
this.els.input.placeholder = value;
|
||||
}
|
||||
},
|
||||
|
||||
clearable: {
|
||||
get: function() { return this._clearable; },
|
||||
set: function(value) {
|
||||
var clearable = !!(value === '' || value);
|
||||
if (clearable === this.clearable) { return; }
|
||||
|
||||
if (clearable) {
|
||||
this.els.inner.setAttribute('clearable', '');
|
||||
this.setAttribute('clearable', '');
|
||||
} else {
|
||||
this.els.inner.removeAttribute('clearable');
|
||||
this.removeAttribute('clearable');
|
||||
}
|
||||
|
||||
this._clearable = clearable;
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.input.value; },
|
||||
set: function(value) { this.els.input.value = value; }
|
||||
},
|
||||
|
||||
required: {
|
||||
get: function() { return this.els.input.required; },
|
||||
set: function(value) { this.els.input.required = value; }
|
||||
},
|
||||
|
||||
maxlength: {
|
||||
get: function() { return this.els.input.getAttribute('maxlength'); },
|
||||
set: function(value) { this.els.input.setAttribute('maxlength', value); }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.input.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.input.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="fields">
|
||||
<input type="text"/>
|
||||
<button class="clear" tabindex="-1"></button>
|
||||
<div class="focus"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
}
|
||||
|
||||
/** Reset
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input,
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] label {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [type='search']
|
||||
*/
|
||||
|
||||
[type='search'] .fields {
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/** Input Field
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
padding: 0 16px;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
resize: none;
|
||||
|
||||
/* Dynamic */
|
||||
|
||||
color:
|
||||
var(--text-color, #000);
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disabled]
|
||||
*/
|
||||
|
||||
input[disabled] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/** Placeholder Text
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::-moz-placeholder {
|
||||
font-style: italic;
|
||||
|
||||
color:
|
||||
var(--input-placeholder-color, #909ca7);
|
||||
}
|
||||
|
||||
/** Clear Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.clear {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
margin: 0 10px;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
background:
|
||||
var(--input-clear-background, #999);
|
||||
}
|
||||
|
||||
/**
|
||||
* [dir=rtl]
|
||||
*/
|
||||
|
||||
[dir=rtl] .clear {
|
||||
right: initial;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* [clearable]
|
||||
*/
|
||||
|
||||
[clearable] .clear {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
input:focus ~ .clear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Clear Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.clear:before {
|
||||
font: normal 500 19px/16.5px 'gaia-icons';
|
||||
content: 'close';
|
||||
display: block;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/** Focus Bar
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.focus {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
transition: all 200ms;
|
||||
transform: scaleX(0);
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
:focus ~ .focus {
|
||||
transform: scaleX(1);
|
||||
transition-delay: 200ms;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input',this));
|
||||
50
gaia/apps/homescreen/bower_components/gaia-text-input/index.html
vendored
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><gaia-text-input></title>
|
||||
<meta name="description" content="gaia-text-input demo">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/base/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-icons/gaia-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-theme/gaia-theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-fonts/style.css">
|
||||
<script src="bower_components/gaia-theme/lib/gaia-theme-selector.js"></script>
|
||||
<script src="bower_components/gaia-component/gaia-component.js"></script>
|
||||
<script src="gaia-text-input.js"></script>
|
||||
<script src="gaia-text-input-multiline.js"></script>
|
||||
<script src="gaia-text-input-pin.js"></script>
|
||||
<style type="text/css">
|
||||
|
||||
body {
|
||||
padding: 48px 24px 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<gaia-theme-selector></gaia-theme-selector>
|
||||
|
||||
<button id="btn">Focus</button>
|
||||
|
||||
|
||||
<gaia-text-input id="field" placeholder="Default"></gaia-text-input>
|
||||
<gaia-text-input placeholder="Search" type="search"></gaia-text-input>
|
||||
<gaia-text-input placeholder="Email" type="email" clearable></gaia-text-input>
|
||||
<gaia-text-input-multiline placeholder="Multiline"></gaia-text-input-multiline>
|
||||
<gaia-text-input-pin length="4"></gaia-text-input-pin>
|
||||
|
||||
<gaia-text-input placeholder="Disabled" disabled></gaia-text-input>
|
||||
<gaia-text-input placeholder="Search" type="search" disabled></gaia-text-input>
|
||||
<gaia-text-input placeholder="Email" type="email" clearable disabled></gaia-text-input>
|
||||
<gaia-text-input-multiline placeholder="Multiline" disabled></gaia-text-input-multiline>
|
||||
<gaia-text-input-pin length="4" disabled></gaia-text-input-pin>
|
||||
|
||||
<!-- <gaia-text-input-password></gaia-text-input-password> -->
|
||||
|
||||
|
||||
<script>
|
||||
btn.onclick = e => field.focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
29
gaia/apps/homescreen/bower_components/gaia-text-input/package.json
vendored
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "gaia-text-input",
|
||||
"version": "0.1.4",
|
||||
"homepage": "https://github.com/gaia-components/gaia-text-input",
|
||||
"license": "ISC",
|
||||
"main": "gaia-text-input.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "^1.3.5",
|
||||
"karma": "^0.12.16",
|
||||
"karma-firefox-launcher": "^0.1.3",
|
||||
"karma-mocha": "^0.1.3",
|
||||
"karma-sinon-chai": "^0.1.6",
|
||||
"mozilla-download": "0.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/karma/bin/karma start test/karma.conf.js --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/karma.conf.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gaia-components/gaia-text-input.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaia-components/gaia-text-input/issues"
|
||||
}
|
||||
}
|
||||
26
gaia/apps/homescreen/bower_components/pressed/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "pressed",
|
||||
"main": "pressed.js",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"homepage": "https://github.com/gaia-components/pressed",
|
||||
"_release": "0a9ae880fe",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "0a9ae880fe71ebb6bffaf14b9fd1c7eb6ce489cb"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/pressed.git",
|
||||
"_target": "master",
|
||||
"_originalSource": "gaia-components/pressed"
|
||||
}
|
||||
4
gaia/apps/homescreen/bower_components/pressed/.jshintrc
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"strict": false,
|
||||
"node": true
|
||||
}
|
||||
17
gaia/apps/homescreen/bower_components/pressed/bower.json
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "pressed",
|
||||
"main": "pressed.js",
|
||||
"version": "0.1.2",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
138
gaia/apps/homescreen/bower_components/pressed/pressed.js
vendored
Executable file
|
|
@ -0,0 +1,138 @@
|
|||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Pointer event abstraction to make
|
||||
* it work for touch and mouse.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var pointer = [
|
||||
{ down: 'touchstart', up: 'touchend', move: 'touchmove' },
|
||||
{ down: 'mousedown', up: 'mouseup', move: 'mousemove' }
|
||||
]['ontouchstart' in window ? 0 : 1];
|
||||
|
||||
module.exports = function(el, options) {
|
||||
var released = (options && options.released) || 200;
|
||||
var scope = (options && options.scope) || el;
|
||||
var min = (options && options.min) || 300;
|
||||
var instant = options && options.instant;
|
||||
var timeouts = {};
|
||||
var removeReleased;
|
||||
|
||||
el.addEventListener(pointer.down, function(e) {
|
||||
var start = e.timeStamp;
|
||||
var target = e.target;
|
||||
var pressed = false;
|
||||
var last = e;
|
||||
|
||||
// If there is a removeRelease pending
|
||||
// run it before we add any more 'pressed'
|
||||
if (removeReleased) { removeReleased(); }
|
||||
|
||||
if (instant) { onPressed(); }
|
||||
else { notScrolling(e, onPressed); }
|
||||
|
||||
function onPressed() {
|
||||
classListUp(target, scope, 'add', 'pressed');
|
||||
}
|
||||
|
||||
addEventListener(pointer.up, function fn(e) {
|
||||
removeEventListener(pointer.up, fn, true);
|
||||
|
||||
var duration = e.timeStamp - start;
|
||||
var delta = min - duration;
|
||||
var lag = Math.max(delta, 0);
|
||||
|
||||
// Once we consider the 'press' event
|
||||
// to be over, we remove the 'pressed'
|
||||
// class and add a 'released' class.
|
||||
timeouts.pressed = setTimeout(function() {
|
||||
classListUp(target, scope, 'remove', 'pressed');
|
||||
classListUp(target, scope, 'add', 'released');
|
||||
|
||||
removeReleased = function() {
|
||||
clearTimeout(timeouts.released);
|
||||
classListUp(target, scope, 'remove', 'released');
|
||||
removeReleased = null;
|
||||
};
|
||||
|
||||
timeouts.released = setTimeout(removeReleased, released);
|
||||
}, lag);
|
||||
}, true);
|
||||
}, true);
|
||||
};
|
||||
|
||||
function notScrolling(e, fn) {
|
||||
detectScrolling(e, function(scrolling) {
|
||||
if (!scrolling) { fn(); }
|
||||
});
|
||||
}
|
||||
|
||||
function detectScrolling(e, fn) {
|
||||
var period = 76;
|
||||
var last = e;
|
||||
|
||||
if (windowScrolling) { return fn(true); }
|
||||
if (!e.touches) { return fn(false); }
|
||||
|
||||
addEventListener('touchmove', onTouchMove, true);
|
||||
setTimeout(detect, period);
|
||||
|
||||
function detect() {
|
||||
removeEventListener('touchmove', onTouchMove, true);
|
||||
if (windowScrolling) { return fn(true); }
|
||||
var time = last.timeStamp - e.timeStamp;
|
||||
var distance = getDistance(e.touches[0], last.touches[0]);
|
||||
var speed = distance / time;
|
||||
var scrolling = speed > 0.03;
|
||||
fn(scrolling);
|
||||
}
|
||||
|
||||
function onTouchMove(e) { last = e; }
|
||||
|
||||
function getDistance(a, b) {
|
||||
var xs = 0;
|
||||
var ys = 0;
|
||||
|
||||
xs = b.clientX - a.clientX;
|
||||
xs = xs * xs;
|
||||
|
||||
ys = b.clientY - a.clientY;
|
||||
ys = ys * ys;
|
||||
|
||||
return Math.sqrt(xs + ys);
|
||||
}
|
||||
}
|
||||
|
||||
var windowScrolling = false;
|
||||
var scrollTimeout;
|
||||
|
||||
// addEventListener('scroll', function() {
|
||||
// windowScrolling = true;
|
||||
// clearTimeout(scrollTimeout);
|
||||
// scrollTimeout = setTimeout(function() {
|
||||
// windowScrolling = false;
|
||||
// }, 60);
|
||||
// });
|
||||
|
||||
/**
|
||||
* Run a classList method on every
|
||||
* element up the DOM tree, until
|
||||
* the given scope.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} scope
|
||||
* @param {String} method
|
||||
* @param {String} cls
|
||||
*/
|
||||
function classListUp(el, scope, method, cls) {
|
||||
while (el && el.classList && el !== scope.parentNode) {
|
||||
el.classList[method](cls);
|
||||
el = el.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('pressed',this));
|
||||
21
gaia/apps/homescreen/build/build.js
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var utils = require('utils');
|
||||
var manager = require('homescreen-manager');
|
||||
|
||||
var HomeScreenAppBuilder = function() {
|
||||
};
|
||||
|
||||
HomeScreenAppBuilder.prototype.execute = function(options) {
|
||||
var homescreen = manager.getHomescreen(options, 'homescreen');
|
||||
var stageDir = utils.getFile(options.STAGE_APP_DIR);
|
||||
var configFile = utils.getFile(stageDir.path, 'js', 'init.json');
|
||||
utils.writeContent(configFile, JSON.stringify(homescreen));
|
||||
};
|
||||
|
||||
exports.execute = function(options) {
|
||||
utils.copyToStage(options);
|
||||
(new HomeScreenAppBuilder()).execute(options);
|
||||
};
|
||||
33
gaia/apps/homescreen/build/default-homescreens.json
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"homescreens": [
|
||||
[
|
||||
["apps", "communications", "dialer"],
|
||||
["apps", "sms"],
|
||||
["apps", "communications", "contacts"],
|
||||
["apps", "email"],
|
||||
["apps", "search"]
|
||||
], [
|
||||
["apps", "camera"],
|
||||
["apps", "gallery"],
|
||||
["apps", "music"],
|
||||
["apps", "video"],
|
||||
["apps", "marketplace.firefox.com"]
|
||||
], [
|
||||
["apps", "calendar"],
|
||||
["apps", "clock"],
|
||||
["apps", "settings"],
|
||||
["apps", "fm"]
|
||||
], [
|
||||
["hosted_apps", "buddyup"],
|
||||
["hosted_apps", "bugzillalite"],
|
||||
["hosted_apps", "facebook"],
|
||||
["hosted_apps", "twitter"],
|
||||
["hosted_apps", "notes"],
|
||||
["hosted_apps", "calculator"],
|
||||
["apps", "costcontrol"]
|
||||
]
|
||||
],
|
||||
"preferences": {
|
||||
"grid.cols": 3
|
||||
}
|
||||
}
|
||||
BIN
gaia/apps/homescreen/icons/128.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
gaia/apps/homescreen/icons/16.png
Executable file
|
After Width: | Height: | Size: 406 B |
BIN
gaia/apps/homescreen/icons/256.png
Executable file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
gaia/apps/homescreen/icons/32.png
Executable file
|
After Width: | Height: | Size: 1,000 B |
BIN
gaia/apps/homescreen/icons/48.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
gaia/apps/homescreen/icons/512.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
gaia/apps/homescreen/icons/60.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
gaia/apps/homescreen/icons/64.png
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
gaia/apps/homescreen/icons/90.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
1
gaia/apps/homescreen/icons/icon.svg
Executable file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M503.5 256c0 136.69-110.81 247.5-247.5 247.5S8.5 392.69 8.5 256 119.31 8.5 256 8.5 503.5 119.31 503.5 256z" fill-opacity=".2"/><path d="M170.564 155.268c-13.73 0-24.863 11.132-24.863 24.833 0 13.743 11.135 24.864 24.835 24.864 13.73 0 24.863-11.106 24.863-24.836 0-13.728-11.133-24.86-24.834-24.86zm83.712 0c-13.728 0-24.863 11.132-24.863 24.833 0 13.743 11.135 24.864 24.834 24.864 13.73 0 24.863-11.106 24.863-24.836 0-13.728-11.133-24.86-24.834-24.86zm83.712 0c-13.714 0-24.863 11.132-24.863 24.833 0 13.743 11.162 24.864 24.835 24.864 13.73 0 24.862-11.106 24.862-24.836 0-13.728-11.132-24.86-24.834-24.86zm-167.424 69.76c-13.73 0-24.863 11.133-24.863 24.835 0 13.742 11.135 24.833 24.835 24.833 13.73 0 24.863-11.094 24.863-24.836 0-13.7-11.133-24.834-24.834-24.834zm83.712 0c-13.728 0-24.863 11.133-24.863 24.835 0 13.742 11.135 24.833 24.834 24.833 13.73 0 24.863-11.094 24.863-24.836 0-13.7-11.133-24.834-24.834-24.834zm83.712 0c-13.714 0-24.863 11.133-24.863 24.835 0 13.742 11.162 24.833 24.835 24.833 13.73 0 24.862-11.094 24.862-24.836 0-13.7-11.132-24.834-24.834-24.834zm-167.424 69.76c-13.73 0-24.863 11.133-24.863 24.835 0 13.7 11.135 24.834 24.835 24.834 13.73 0 24.863-11.106 24.863-24.836 0-13.727-11.133-24.833-24.834-24.833zm83.712 0c-13.728 0-24.863 11.133-24.863 24.835 0 13.7 11.135 24.834 24.834 24.834 13.73 0 24.863-11.106 24.863-24.836 0-13.727-11.133-24.833-24.834-24.833zm83.712 0c-13.714 0-24.863 11.133-24.863 24.835 0 13.7 11.162 24.834 24.835 24.834 13.73 0 24.862-11.106 24.862-24.836 0-13.727-11.132-24.833-24.834-24.833zm-167.424 69.76c-13.73 0-24.863 11.132-24.863 24.833 0 13.702 11.135 24.837 24.835 24.837 13.73 0 24.863-11.107 24.863-24.836 0-13.727-11.133-24.833-24.834-24.833zm83.712 0c-13.728 0-24.863 11.132-24.863 24.833 0 13.702 11.135 24.837 24.834 24.837 13.73 0 24.863-11.107 24.863-24.836 0-13.727-11.133-24.833-24.834-24.833zm83.712 0c-13.714 0-24.863 11.132-24.863 24.833 0 13.702 11.162 24.837 24.835 24.837 13.73 0 24.862-11.107 24.862-24.836 0-13.727-11.132-24.833-24.834-24.833zm25.49-266.765H148.52c-4.925 0-8.93 6.28-8.93 14.022 0 7.743 4.005 14.05 8.93 14.05h214.96c4.94 0 8.93-6.28 8.93-14.05 0-7.743-4.005-14.022-8.93-14.022z" fill="#fff" fill-opacity=".8"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
114
gaia/apps/homescreen/index.html
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta name="theme-color" content="transparent">
|
||||
<title>Firefox OS Home Screen</title>
|
||||
|
||||
<!-- Setup environment -->
|
||||
<script defer src="js/setup.js"></script>
|
||||
|
||||
<!-- Lazy loader -->
|
||||
<script defer src="shared/js/lazy_loader.js"></script>
|
||||
<!-- <script defer src="js/firstrun.js"></script> -->
|
||||
|
||||
<!-- gaia-container -->
|
||||
<script defer src="bower_components/gaia-container/script.js"></script>
|
||||
|
||||
<!-- gaia-site-icon -->
|
||||
<link rel="stylesheet" href="shared/elements/gaia-site-icon/external/style.css">
|
||||
<script defer src="shared/elements/gaia-site-icon/script.js"></script>
|
||||
|
||||
<!-- gaia-pin-card -->
|
||||
<script defer src="shared/js/component_utils.js"></script>
|
||||
<script defer src="shared/elements/gaia_pin_card/script.js"></script>
|
||||
|
||||
<!-- gaia-components -->
|
||||
<link rel="stylesheet" href="bower_components/base/base.css">
|
||||
<link rel="stylesheet" href="shared/elements/gaia-theme/gaia-theme.css">
|
||||
<script defer src="bower_components/evt/index.js"></script>
|
||||
<script defer src="bower_components/pressed/pressed.js"></script>
|
||||
<script defer src="shared/elements/gaia-component/gaia-component.js"></script>
|
||||
<script defer src="bower_components/gaia-dialog/gaia-dialog.js"></script>
|
||||
<script defer src="bower_components/gaia-dialog/gaia-dialog-action.js"></script>
|
||||
|
||||
<!-- gaia l10n -->
|
||||
<script defer src="shared/js/l10n.js"></script>
|
||||
|
||||
<!-- Local resources -->
|
||||
<link rel="localization" href="locales/homescreen.{locale}.properties">
|
||||
|
||||
<link rel="stylesheet" href="style/window.css">
|
||||
<link rel="stylesheet" href="style/grid.css">
|
||||
<link rel="stylesheet" href="style/apps-panel.css">
|
||||
<link rel="stylesheet" href="style/pages-panel.css">
|
||||
|
||||
<!-- Process the icons of pinned pages -->
|
||||
<script defer src="shared/elements/gaia_grid/js/grid_icon_renderer.js"></script>
|
||||
<script defer src="shared/js/homescreens/icon.js"></script>
|
||||
<script defer src="shared/js/icons_helper.js"></script>
|
||||
|
||||
<script defer src="js/settings.js"></script>
|
||||
<script defer src="js/appsmetadata.js"></script>
|
||||
<script defer src="js/datastore.js"></script>
|
||||
<script defer src="js/pagesstore.js"></script>
|
||||
<script defer src="js/pages.js"></script>
|
||||
<script defer src="js/apps.js"></script>
|
||||
<script defer src="js/window.js"></script>
|
||||
<script defer src="js/bootstrap.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header id="page-indicator-header">
|
||||
<div id="page-indicator" data-l10n-id="page-indicator-text" role="slider" aria-valuemin="1" aria-valuenow="1" aria-valuemax="2" aria-controls="panels">
|
||||
<span></span><span></span>
|
||||
</div>
|
||||
</header>
|
||||
<div id="shadow"></div>
|
||||
<div id="panels"><!--
|
||||
--><div id="apps-panel" aria-labelledby="page-indicator-header">
|
||||
<div class="scrollable">
|
||||
<div class="edit-underlay"></div>
|
||||
<gaia-container id="apps" drag-and-drop></gaia-container>
|
||||
</div>
|
||||
<gaia-dialog id="cancel-download" class="dialog-action">
|
||||
<section>
|
||||
<p class="body" data-l10n-id="stop-download-body"></p>
|
||||
</section>
|
||||
<button class="action" data-l10n-id="stop-download-action"></button>
|
||||
<button on-click="close" data-l10n-id="cancel-action"></button>
|
||||
</gaia-dialog>
|
||||
<gaia-dialog id="resume-download" class="dialog-action">
|
||||
<section>
|
||||
<p class="body" data-l10n-id="resume-download-body"></p>
|
||||
</section>
|
||||
<button class="action" data-l10n-id="resume-download-action"></button>
|
||||
<button on-click="close" data-l10n-id="cancel-action"></button>
|
||||
</gaia-dialog>
|
||||
</div><!--
|
||||
--><div id="pages-panel" aria-labelledby="page-indicator-header">
|
||||
<div id="empty-pages">
|
||||
<h1 data-l10n-id="no-pinned-pages-header"></h1>
|
||||
<p data-l10n-id="no-pinned-pages-body"></p>
|
||||
<img src="style/images/empty_home_image.png" role="presentation"></img>
|
||||
<div id="boxes" role="presentation">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollable">
|
||||
<div class="edit-underlay"></div>
|
||||
<gaia-container id="pages"></gaia-container>
|
||||
</div>
|
||||
</div><!--
|
||||
--></div>
|
||||
<div id="bottombar">
|
||||
<div id="remove"></div>
|
||||
<div id="rename"></div>
|
||||
<div id="done" data-l10n-id="edit-done"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1128
gaia/apps/homescreen/js/apps.js
Executable file
110
gaia/apps/homescreen/js/appsmetadata.js
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
const DB_NAME = 'home-metadata';
|
||||
const DB_ORDER_STORE = 'order';
|
||||
const DB_ICON_STORE = 'icon';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
function AppsMetadata() {}
|
||||
|
||||
AppsMetadata.prototype = {
|
||||
db: null,
|
||||
|
||||
init: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var req = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||
req.onupgradeneeded = this.upgradeSchema;
|
||||
req.onsuccess = (e) => {
|
||||
this.db = e.target.result;
|
||||
resolve();
|
||||
};
|
||||
req.onerror = (e) => {
|
||||
console.error('Error opening homescreen metadata db', e);
|
||||
reject(e);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
upgradeSchema: function(e) {
|
||||
var db = e.target.result;
|
||||
var fromVersion = e.oldVersion;
|
||||
if (fromVersion < 1) {
|
||||
var store = db.createObjectStore(DB_ORDER_STORE, { keyPath: 'id' });
|
||||
store.createIndex('order', 'order', { unique: false });
|
||||
store = db.createObjectStore(DB_ICON_STORE, { keyPath: 'id' });
|
||||
store.createIndex('icon', 'icon', { unique: false });
|
||||
}
|
||||
},
|
||||
|
||||
set: function(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([DB_ORDER_STORE, DB_ICON_STORE],
|
||||
'readwrite');
|
||||
for (var entry of data) {
|
||||
if (!entry.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof entry.order !== 'undefined') {
|
||||
txn.objectStore(DB_ORDER_STORE).
|
||||
put({ id: entry.id, order: entry.order });
|
||||
}
|
||||
if (typeof entry.icon !== 'undefined') {
|
||||
txn.objectStore(DB_ICON_STORE).
|
||||
put({ id: entry.id, icon: entry.icon });
|
||||
}
|
||||
}
|
||||
txn.oncomplete = resolve;
|
||||
txn.onerror = reject;
|
||||
});
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([DB_ORDER_STORE, DB_ICON_STORE],
|
||||
'readwrite');
|
||||
txn.objectStore(DB_ORDER_STORE).delete(id);
|
||||
txn.objectStore(DB_ICON_STORE).delete(id);
|
||||
txn.oncomplete = resolve;
|
||||
txn.onerror = reject;
|
||||
});
|
||||
},
|
||||
|
||||
getAll: function(onResult) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([DB_ORDER_STORE, DB_ICON_STORE],
|
||||
'readonly');
|
||||
var orderStore = txn.objectStore(DB_ORDER_STORE);
|
||||
var iconStore = txn.objectStore(DB_ICON_STORE);
|
||||
var cursor = orderStore.index('order').openCursor();
|
||||
var results = [];
|
||||
|
||||
cursor.onsuccess = e => {
|
||||
var cursor = e.target.result;
|
||||
if (cursor) {
|
||||
var result = cursor.value;
|
||||
var iconRequest = iconStore.get(result.id);
|
||||
iconRequest.onsuccess = function(result, e) {
|
||||
if (e.target.result) {
|
||||
result.icon = e.target.result.icon;
|
||||
}
|
||||
results.push(result);
|
||||
if (onResult) {
|
||||
onResult(result);
|
||||
}
|
||||
}.bind(this, result);
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
txn.oncomplete = () => {
|
||||
resolve(results);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.AppsMetadata = AppsMetadata;
|
||||
|
||||
}(window));
|
||||
5
gaia/apps/homescreen/js/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
/* global AppWindow */
|
||||
/* exported appWindow */
|
||||
'use strict';
|
||||
|
||||
var appWindow = new AppWindow();
|
||||
234
gaia/apps/homescreen/js/datastore.js
Executable file
|
|
@ -0,0 +1,234 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
function Datastore(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Datastore.prototype = {
|
||||
/**
|
||||
* The name of the datastore
|
||||
*/
|
||||
name: '',
|
||||
|
||||
/**
|
||||
* The version of the indexed database
|
||||
*/
|
||||
DB_VERSION: 1,
|
||||
|
||||
/**
|
||||
* The datastore
|
||||
*/
|
||||
datastore: null,
|
||||
|
||||
/**
|
||||
* The last datastore revision that was synced
|
||||
*/
|
||||
lastRevision: null,
|
||||
|
||||
/**
|
||||
* Our local indexed db where we store our copy of bookmarks
|
||||
*/
|
||||
db: null,
|
||||
|
||||
get revisionName() {
|
||||
return this.name + '_revision';
|
||||
},
|
||||
|
||||
get idbName() {
|
||||
return this.name + '_mirror';
|
||||
},
|
||||
|
||||
get storeName() {
|
||||
return this.name + '_store';
|
||||
},
|
||||
|
||||
init: function() {
|
||||
var revisionString = localStorage.getItem(this.revisionName);
|
||||
if (revisionString) {
|
||||
this.lastRevision = JSON.parse(revisionString);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var req = window.indexedDB.open(this.idbName, this.DB_VERSION);
|
||||
req.onupgradeneeded = this.upgradeSchema.bind(this);
|
||||
req.onsuccess = (e) => {
|
||||
this.db = e.target.result;
|
||||
resolve();
|
||||
|
||||
// Open up the shared datastore
|
||||
if (!navigator.getDataStores) {
|
||||
console.error('DataStore API is unavailable');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.getDataStores(this.name).then((stores) => {
|
||||
if (stores.length < 1) {
|
||||
console.error(this.name + ' inaccessible');
|
||||
return;
|
||||
}
|
||||
|
||||
this.datastore = stores[0];
|
||||
this.datastore.addEventListener('change',
|
||||
this.onChange.bind(this));
|
||||
this.synchronise();
|
||||
}, (e) => {
|
||||
console.error('Error getting datastore', e);
|
||||
});
|
||||
};
|
||||
req.onerror = (e) => {
|
||||
console.error('Error opening datastore mirror database', e);
|
||||
reject(e);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
upgradeSchema: function(e) {
|
||||
var db = e.target.result;
|
||||
var fromVersion = e.oldVersion;
|
||||
if (fromVersion < 1) {
|
||||
var store = db.createObjectStore(this.storeName, { keyPath: 'id' });
|
||||
store.createIndex('data', 'data', { unique: false });
|
||||
}
|
||||
},
|
||||
|
||||
synchronise: function() {
|
||||
// Serialise mirror refreshes: overlapping clear/add snapshots erase
|
||||
// rows while listeners are reading them.
|
||||
this._syncQueue = (this._syncQueue || Promise.resolve()).then(
|
||||
() => this._synchronise());
|
||||
return this._syncQueue;
|
||||
},
|
||||
_synchronise: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var cursor = this.datastore.sync(this.lastRevision);
|
||||
|
||||
var self = this;
|
||||
function cursorResolve(task) {
|
||||
var promises = [];
|
||||
switch (task.operation) {
|
||||
case 'update':
|
||||
case 'add':
|
||||
promises.push(self.set(task.data));
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
promises.push(self.remove(task.id));
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
promises.push(self.clear());
|
||||
break;
|
||||
|
||||
case 'done':
|
||||
self.updateRevision();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
promises.push(cursor.next());
|
||||
Promise.all(promises).then(
|
||||
(results) => {
|
||||
cursorResolve(results.pop());
|
||||
}, reject);
|
||||
}
|
||||
|
||||
cursor.next().then(cursorResolve, reject);
|
||||
});
|
||||
},
|
||||
|
||||
set: function(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([this.storeName], 'readwrite');
|
||||
txn.oncomplete = function onComplete(data, resolve) {
|
||||
resolve();
|
||||
document.dispatchEvent(new CustomEvent(this.name + '-set',
|
||||
{ detail: { id: data.id }}));
|
||||
}.bind(this, data, resolve);
|
||||
txn.onerror = reject;
|
||||
try {
|
||||
txn.objectStore(this.storeName).put({ id: data.id, data: data });
|
||||
} catch(e) {
|
||||
console.error('Error putting data in ' + this.idbName + ':', e);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([this.storeName], 'readwrite');
|
||||
txn.oncomplete = function onComplete(id, resolve) {
|
||||
resolve();
|
||||
document.dispatchEvent(new CustomEvent(this.name + '-removed',
|
||||
{ detail: { id: id }}));
|
||||
}.bind(this, id, resolve);
|
||||
txn.onerror = reject;
|
||||
try {
|
||||
txn.objectStore(this.storeName).delete(id);
|
||||
} catch(e) {
|
||||
console.error('Error deleting data from ' + this.idbName + ':', e);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([this.storeName], 'readwrite');
|
||||
txn.oncomplete = function onComplete(resolve) {
|
||||
resolve();
|
||||
document.dispatchEvent(new CustomEvent(this.name + '-cleared'));
|
||||
}.bind(this, resolve);
|
||||
txn.onerror = reject;
|
||||
txn.objectStore(this.storeName).clear();
|
||||
});
|
||||
},
|
||||
|
||||
updateRevision: function() {
|
||||
this.lastRevision = this.datastore.revisionId;
|
||||
localStorage.setItem(this.revisionName,
|
||||
JSON.stringify(this.lastRevision));
|
||||
},
|
||||
|
||||
onChange: function(e) {
|
||||
this.synchronise().then(() => {},
|
||||
(error) => {
|
||||
console.error('Failed to handle ' + this.name + ' change', error);
|
||||
});
|
||||
},
|
||||
|
||||
get: function(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([this.storeName], 'readonly');
|
||||
txn.onerror = reject;
|
||||
txn.objectStore(this.storeName).get(id).onsuccess =
|
||||
(event) => {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getAll: function(filter = () => true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var results = [];
|
||||
var txn = this.db.transaction([this.storeName], 'readonly');
|
||||
txn.onerror = reject;
|
||||
txn.oncomplete = () => { resolve(results); };
|
||||
txn.objectStore(this.storeName).openCursor().onsuccess =
|
||||
(event) => {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (filter(cursor.value)) {
|
||||
results.push(cursor.value);
|
||||
}
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.Datastore = Datastore;
|
||||
|
||||
}(window));
|
||||
180
gaia/apps/homescreen/js/firstrun.js
Executable file
|
|
@ -0,0 +1,180 @@
|
|||
/* global LazyLoader */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const COLS_PREF = 'grid.cols';
|
||||
|
||||
const VERTICALHOME_APPS = 'verticalhome_items_migration';
|
||||
|
||||
const VERTICALHOME_SETTINGS = 'vertical_preferences_store';
|
||||
|
||||
const HOMESCREEN_SETTINGS = 'homescreen_settings';
|
||||
|
||||
function FirstRun() {
|
||||
return importVerticalHome().then(Promise.resolve(),
|
||||
(e) => {
|
||||
console.warn('Loading default settings, import failed:', e);
|
||||
return loadDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function importVerticalHome() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!navigator.getDataStores) {
|
||||
reject('DataStore API is unavailable');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.getDataStores(VERTICALHOME_APPS).then(
|
||||
stores => {
|
||||
if (stores.length < 1) {
|
||||
reject('verticalhome datastores inaccessible');
|
||||
return;
|
||||
}
|
||||
|
||||
stores[0].getLength().then(length => {
|
||||
if (length === 0) {
|
||||
reject('No verticalhome app order to import');
|
||||
return;
|
||||
}
|
||||
|
||||
var cursor = stores[0].sync();
|
||||
|
||||
var items = {};
|
||||
var iterateItems = () => {
|
||||
cursor.next().then(task => {
|
||||
switch (task.operation) {
|
||||
case 'done':
|
||||
// Convert from verticalhome storage format to something
|
||||
// homescreen can use, skipping over features that aren't
|
||||
// supported.
|
||||
var id;
|
||||
var order = [];
|
||||
for (var prop in items) {
|
||||
var item = items[prop];
|
||||
switch (item.type) {
|
||||
case 'app':
|
||||
id = item.manifestURL + '/' +
|
||||
(item.entryPoint ? item.entryPoint : '');
|
||||
break;
|
||||
|
||||
case 'bookmark':
|
||||
id = item.id;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
order.push({ id: id, order: item.index });
|
||||
}
|
||||
|
||||
// Sort by order
|
||||
order.sort((a, b) => { return a.order - b.order; });
|
||||
|
||||
// Rewrite order so there are no gaps
|
||||
order.forEach((entry, index) => {
|
||||
entry.order = index;
|
||||
});
|
||||
|
||||
// Load columns setting and resolve
|
||||
navigator.getDataStores(VERTICALHOME_SETTINGS).then(
|
||||
stores => {
|
||||
stores[0].get(COLS_PREF).then(cols => {
|
||||
resolve({ order: order, small: cols >= 4 });
|
||||
|
||||
// If 4-column is set, we should synchronise the
|
||||
// homescreen settings datastore so that Settings
|
||||
// reflects the correct value. (3 is the default)
|
||||
if (cols >= 4) {
|
||||
navigator.getDataStores(HOMESCREEN_SETTINGS).then(
|
||||
stores => {
|
||||
stores[0].put(cols, COLS_PREF);
|
||||
}).catch(
|
||||
e => {
|
||||
console.error(
|
||||
'Error storing homescreen cols setting', e);
|
||||
});
|
||||
}
|
||||
}, () => {
|
||||
resolve({ order: order, small: false });
|
||||
});
|
||||
},
|
||||
(e) => {
|
||||
console.error('Error retrieving columns preference', e);
|
||||
resolve({ order: order, small: false });
|
||||
});
|
||||
return;
|
||||
|
||||
case 'clear':
|
||||
items = {};
|
||||
break;
|
||||
|
||||
case 'put':
|
||||
case 'add':
|
||||
items[task.id] = task.data;
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
delete items[task.id];
|
||||
break;
|
||||
|
||||
default:
|
||||
reject('Unhandled datastore operation: ' + task.operation);
|
||||
return;
|
||||
}
|
||||
|
||||
iterateItems();
|
||||
}, reject);
|
||||
};
|
||||
|
||||
iterateItems();
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function loadDefault() {
|
||||
return new Promise((resolve, reject) => {
|
||||
LazyLoader.getJSON('js/init.json').then(
|
||||
json => {
|
||||
if (!json) {
|
||||
resolve({ order: [], small: false});
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the initialisation JSON format to something more useful
|
||||
// for homescreen.
|
||||
var small = !!(json.preferences && json.preferences[COLS_PREF] &&
|
||||
(json.preferences[COLS_PREF] > 3));
|
||||
|
||||
var order = [];
|
||||
if (json.grid) {
|
||||
var index = 0;
|
||||
json.grid.forEach((section) => {
|
||||
section.forEach((entry) => {
|
||||
if (!entry || !entry.manifestURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = entry.manifestURL + '/' +
|
||||
(entry.entry_point ? entry.entry_point : '');
|
||||
order.push({ id: id, order: index });
|
||||
index ++;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resolve({ order: order, small: small});
|
||||
},
|
||||
e => {
|
||||
reject(e);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
exports.FirstRun = FirstRun;
|
||||
|
||||
}(window));
|
||||
272
gaia/apps/homescreen/js/pages.js
Executable file
|
|
@ -0,0 +1,272 @@
|
|||
/* global PagesStore, IconsHelper */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* The size in pixels of the icons in the pinned pages.
|
||||
*/
|
||||
const PAGES_ICON_SIZE = 30;
|
||||
|
||||
function Pages() {
|
||||
// Element references
|
||||
this.panel = document.getElementById('pages-panel');
|
||||
this.panels = document.getElementById('panels');
|
||||
this.pages = document.getElementById('pages');
|
||||
this.scrollable = document.querySelector('#pages-panel > .scrollable');
|
||||
this.bottombar = document.getElementById('bottombar');
|
||||
this.remove = document.getElementById('remove');
|
||||
this.done = document.getElementById('done');
|
||||
|
||||
// Tracking if the list is empty
|
||||
this.empty = true;
|
||||
|
||||
// Edit mode
|
||||
this.editMode = false;
|
||||
|
||||
// Dialogs
|
||||
this.dialogs = [];
|
||||
|
||||
// Signal handlers
|
||||
this.pages.addEventListener('click', this);
|
||||
this.pages.addEventListener('contextmenu', this);
|
||||
this.pages.addEventListener('keydown', this);
|
||||
window.addEventListener('resize', this);
|
||||
|
||||
this.done.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
this.exitEditMode();
|
||||
});
|
||||
|
||||
this.remove.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
this.unpinSelectedCard();
|
||||
});
|
||||
|
||||
// Initialise and populate pinned pages
|
||||
this.pagesStore = new PagesStore('places');
|
||||
this.pagesStore.init().then(() => {
|
||||
// Triggered when pages are added and updated.
|
||||
document.addEventListener('places-set', (e) => {
|
||||
var id = e.detail.id;
|
||||
this.pagesStore.get(id).then((page) => {
|
||||
for (var child of this.pages.children) {
|
||||
if (child.dataset.id === id) {
|
||||
if (!page.data.pinned) {
|
||||
this.removeCard(child);
|
||||
} else {
|
||||
this.updatePinnedPage(child, page.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!page.data.pinned) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A new page was pinned.
|
||||
this.addPinnedPage(page.data);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('places-removed', (e) => {
|
||||
var id = e.detail.id;
|
||||
for (var child of this.pages.children) {
|
||||
if (child.dataset.id === id) {
|
||||
this.removeCard(child);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('places-cleared', () => {
|
||||
this.exitEditMode();
|
||||
|
||||
for (var child of this.pages.children) {
|
||||
this.pages.removeChild(child);
|
||||
}
|
||||
|
||||
if (!this.empty) {
|
||||
this.empty = true;
|
||||
this.panel.classList.add('empty');
|
||||
}
|
||||
});
|
||||
},
|
||||
(e) => {
|
||||
console.error('Error initialising pinned pages', e);
|
||||
}).then(() => {
|
||||
return this.pagesStore.getAll().then((pages) => {
|
||||
for (var page of pages) {
|
||||
this.addPinnedPage(page.data);
|
||||
}
|
||||
|
||||
// If there are no pinned pages, display helpful information.
|
||||
if (this.empty) {
|
||||
this.panel.classList.add('empty');
|
||||
}
|
||||
}, (e) => {
|
||||
console.error('Error getting pinned pages', e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Pages.prototype = {
|
||||
updatePinnedPage: function(card, page) {
|
||||
card.title = page.title;
|
||||
card.dataset.id = page.url;
|
||||
card.style.order = -Math.round(page.pinTime / 1000);
|
||||
page.meta = page.meta || {};
|
||||
|
||||
if (page.screenshot) {
|
||||
page.meta.screenshot = page.screenshot;
|
||||
}
|
||||
|
||||
setTimeout(function(card, page) {
|
||||
card.meta = page.meta;
|
||||
IconsHelper.getIconBlob(page.url, PAGES_ICON_SIZE, page)
|
||||
.then((iconObj) => {
|
||||
if (iconObj.blob) {
|
||||
var iconURL = URL.createObjectURL(iconObj.blob);
|
||||
card.icon = `url(${iconURL})`;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Failed to fetch icon', e);
|
||||
});
|
||||
}.bind(this, card, page));
|
||||
},
|
||||
|
||||
addPinnedPage: function(page) {
|
||||
var pinCard = document.createElement('gaia-pin-card');
|
||||
|
||||
// Make the card accessible/allow activation
|
||||
pinCard.tabIndex = 0;
|
||||
pinCard.setAttribute('role', 'link');
|
||||
|
||||
this.updatePinnedPage(pinCard, page);
|
||||
this.pages.appendChild(pinCard);
|
||||
|
||||
if (this.empty) {
|
||||
this.empty = false;
|
||||
this.panel.classList.remove('empty');
|
||||
}
|
||||
},
|
||||
|
||||
launchCard: function(card) {
|
||||
var features = {
|
||||
name: card.title,
|
||||
icon: card.icon,
|
||||
remote: true
|
||||
};
|
||||
|
||||
window.open(card.dataset.id, '_blank', Object.keys(features).map(
|
||||
key => encodeURIComponent(key) + '=' + encodeURIComponent(features[key])
|
||||
).join(','));
|
||||
},
|
||||
|
||||
enterEditMode: function(card) {
|
||||
if (this.selectedCard) {
|
||||
this.selectedCard.classList.remove('selected');
|
||||
}
|
||||
if (card) {
|
||||
this.selectedCard = card;
|
||||
card.classList.add('selected');
|
||||
}
|
||||
|
||||
this.editMode = true;
|
||||
document.body.classList.add('edit-mode');
|
||||
this.remove.classList.add('active');
|
||||
},
|
||||
|
||||
exitEditMode: function() {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editMode = false;
|
||||
document.body.classList.remove('edit-mode');
|
||||
this.remove.classList.remove('active');
|
||||
if (this.selectedCard) {
|
||||
this.selectedCard.classList.remove('selected');
|
||||
this.selectedCard = null;
|
||||
}
|
||||
},
|
||||
|
||||
removeCard: function(card) {
|
||||
if (this.selectedCard === card) {
|
||||
this.selectedCard = null;
|
||||
}
|
||||
this.pages.removeChild(card, () => {
|
||||
if (!this.empty && this.pages.children.length === 0) {
|
||||
this.exitEditMode();
|
||||
this.empty = true;
|
||||
this.panel.classList.add('empty');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
unpinSelectedCard: function() {
|
||||
if (!this.selectedCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = this.selectedCard.dataset.id;
|
||||
|
||||
// Remove child immediately, datastore operations can be quite slow
|
||||
this.removeCard(this.selectedCard);
|
||||
|
||||
this.pagesStore.get(id).then(entry => {
|
||||
entry.data.pinned = false;
|
||||
this.pagesStore.datastore.put(entry.data, id).then(() => {},
|
||||
e => {
|
||||
console.error('Error unpinning page:', e);
|
||||
});
|
||||
}, e => {
|
||||
console.error('Error retrieving page to unpin:', e);
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function(e) {
|
||||
switch (e.type) {
|
||||
case 'click':
|
||||
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.editMode) {
|
||||
this.enterEditMode(e.target);
|
||||
} else {
|
||||
this.launchCard(e.target);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contextmenu':
|
||||
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
|
||||
break;
|
||||
}
|
||||
this.enterEditMode(e.target);
|
||||
break;
|
||||
|
||||
case 'keydown':
|
||||
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 32: // Space
|
||||
case 13: // Enter
|
||||
this.launchCard(e.target);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'resize':
|
||||
this.pages.synchronise();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Pages = Pages;
|
||||
|
||||
}(window));
|
||||
25
gaia/apps/homescreen/js/pagesstore.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
/* global Datastore */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
function PagesStore(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
PagesStore.prototype = Object.create(Datastore.prototype);
|
||||
|
||||
PagesStore.prototype.DB_VERSION = 1;
|
||||
|
||||
PagesStore.prototype.set = function(data) {
|
||||
data.id = data.url;
|
||||
return Datastore.prototype.set.call(this, data);
|
||||
};
|
||||
|
||||
PagesStore.prototype.getAll = function() {
|
||||
var filter = (value) => value.data && value.data.pinned;
|
||||
return Datastore.prototype.getAll.call(this, filter);
|
||||
};
|
||||
|
||||
exports.PagesStore = PagesStore;
|
||||
|
||||
}(window));
|
||||
118
gaia/apps/homescreen/js/settings.js
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Name of the global home screen settings datastore.
|
||||
*/
|
||||
const SETTINGS_STORE = 'homescreen_settings';
|
||||
|
||||
/**
|
||||
* Stored settings version, for use when changing/refactoring settings
|
||||
* storage.
|
||||
*/
|
||||
const SETTINGS_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Name of the global home screen columns setting.
|
||||
*/
|
||||
const COLUMNS_SETTING = 'grid.cols';
|
||||
|
||||
/**
|
||||
* Name of the global home screen paging setting.
|
||||
*/
|
||||
const PAGING_SETTING = 'grid.paging';
|
||||
|
||||
function Settings() {
|
||||
// Initialise default settings
|
||||
this.small = false;
|
||||
this.scrollSnapping = false;
|
||||
this.firstRun = false;
|
||||
|
||||
// Listen to external settings changes
|
||||
if (navigator.getDataStores) {
|
||||
navigator.getDataStores(SETTINGS_STORE).then(stores => {
|
||||
if (stores.length < 1) {
|
||||
return Promise.reject('Settings datastore inaccessible');
|
||||
}
|
||||
|
||||
var signalChange = () => {
|
||||
this.save();
|
||||
window.dispatchEvent(new CustomEvent('settings-changed'));
|
||||
};
|
||||
|
||||
var syncSmallSetting = (signal) => {
|
||||
stores[0].get(COLUMNS_SETTING).then(cols => {
|
||||
var oldSmall = this.small;
|
||||
this.small = cols > 3;
|
||||
if (this.small !== oldSmall) {
|
||||
signalChange();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var syncPagingSetting = () => {
|
||||
stores[0].get(PAGING_SETTING).then(paging => {
|
||||
var oldSnapping = this.scrollSnapping;
|
||||
this.scrollSnapping = paging || false;
|
||||
if (this.scrollSnapping !== oldSnapping) {
|
||||
signalChange();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
stores[0].addEventListener('change', e => {
|
||||
switch (e.id) {
|
||||
case COLUMNS_SETTING:
|
||||
syncSmallSetting();
|
||||
break;
|
||||
case PAGING_SETTING:
|
||||
syncPagingSetting();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
syncSmallSetting();
|
||||
syncPagingSetting();
|
||||
}, e => {
|
||||
console.error('Error retrieving home screen settings datastore:', e);
|
||||
});
|
||||
} else {
|
||||
console.error('Datastore API unavailable');
|
||||
}
|
||||
|
||||
// Restore existing settings
|
||||
var settingsString = localStorage.getItem('settings');
|
||||
if (!settingsString) {
|
||||
this.firstRun = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = JSON.parse(settingsString);
|
||||
if (settings.version !== SETTINGS_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.small = settings.small || false;
|
||||
this.scrollSnapping = settings.scrollSnapping || false;
|
||||
|
||||
// Monitor global homescreen settings
|
||||
if (!navigator.getDataStores) {
|
||||
console.error('Datastore API unavailable');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Settings.prototype = {
|
||||
save: function() {
|
||||
localStorage.setItem('settings', JSON.stringify({
|
||||
version: SETTINGS_VERSION,
|
||||
small: this.small,
|
||||
scrollSnapping: this.scrollSnapping
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
exports.Settings = Settings;
|
||||
|
||||
}(window));
|
||||
1
gaia/apps/homescreen/js/setup.js
Executable file
|
|
@ -0,0 +1 @@
|
|||
window.GaiaAppIconBaseurl = 'shared/elements/gaia-site-icon/';
|
||||
175
gaia/apps/homescreen/js/window.js
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
/* global Apps, Pages */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Time after receiving a handled hashchange that we ignore hashchange
|
||||
* events for.
|
||||
*/
|
||||
const HASH_CHANGE_DEBOUNCE = 100;
|
||||
|
||||
function AppWindow() {
|
||||
this.header = document.getElementById('page-indicator-header');
|
||||
this.indicator = document.getElementById('page-indicator');
|
||||
this.panels = document.getElementById('panels');
|
||||
this.shadow = document.getElementById('shadow');
|
||||
|
||||
this.apps = new Apps();
|
||||
this.pages = new Pages();
|
||||
|
||||
// Panel visibility state
|
||||
this.appsVisible = undefined;
|
||||
|
||||
// Home-button handling
|
||||
this.ignoreHashChangeTimeout = null;
|
||||
|
||||
// Signal handlers
|
||||
this.indicator.addEventListener('keypress', this);
|
||||
this.panels.addEventListener('scroll', this);
|
||||
window.addEventListener('hashchange', this, true);
|
||||
window.addEventListener('localized', this);
|
||||
document.addEventListener('visibilitychange', this);
|
||||
|
||||
// Update the panel indicator
|
||||
this.updatePanelIndicator();
|
||||
|
||||
// Application has finished initialisation
|
||||
window.performance.mark('navigationInteractive');
|
||||
}
|
||||
|
||||
AppWindow.prototype = {
|
||||
updatePanelIndicator: function() {
|
||||
var appsVisible =
|
||||
Math.abs(this.panels.scrollLeft) <= this.panels.scrollWidth / 4;
|
||||
if (this.appsVisible !== appsVisible) {
|
||||
this.appsVisible = appsVisible;
|
||||
|
||||
this.header.setAttribute('data-l10n-id', appsVisible ?
|
||||
'apps-panel' : 'pages-panel');
|
||||
|
||||
this.indicator.children[0].classList.toggle('active', appsVisible);
|
||||
this.indicator.children[1].classList.toggle('active', !appsVisible);
|
||||
this.indicator.setAttribute('aria-valuenow', appsVisible ? 1 : 2);
|
||||
this.indicator.setAttribute('data-l10n-args', JSON.stringify({
|
||||
currentPage: appsVisible ? 1 : 2,
|
||||
totalPages: 2
|
||||
}));
|
||||
|
||||
this.apps.panel.setAttribute('aria-hidden', !appsVisible);
|
||||
this.pages.panel.setAttribute('aria-hidden', appsVisible);
|
||||
|
||||
if (appsVisible) {
|
||||
this.pages.scrollable.removeEventListener('scroll', this);
|
||||
this.apps.scrollable.addEventListener('scroll', this);
|
||||
} else {
|
||||
this.apps.scrollable.removeEventListener('scroll', this);
|
||||
this.pages.scrollable.addEventListener('scroll', this);
|
||||
}
|
||||
|
||||
this.updateShadowState();
|
||||
}
|
||||
},
|
||||
|
||||
updateShadowState: function() {
|
||||
var position = this.appsVisible ?
|
||||
this.apps.scrollable.scrollTop : this.pages.scrollable.scrollTop;
|
||||
var scrolled = position > 1;
|
||||
if (this.shadow.classList.contains('visible') !== scrolled) {
|
||||
this.shadow.classList.toggle('visible', scrolled);
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(e) {
|
||||
switch (e.type) {
|
||||
// Switch between panels
|
||||
case 'keypress':
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case e.DOM_VK_RIGHT:
|
||||
this.panels.scrollTo(
|
||||
{ left: this.panels.scrollLeftMax, top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
case e.DOM_VK_LEFT:
|
||||
this.panels.scrollTo(
|
||||
{ left: 0, top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
// Display the top shadow when scrolling down
|
||||
case 'scroll':
|
||||
if (e.target === this.panels) {
|
||||
this.updatePanelIndicator();
|
||||
} else {
|
||||
this.updateShadowState();
|
||||
}
|
||||
return;
|
||||
|
||||
// Cancel edit modes/scroll to the top/scroll to apps on home-button
|
||||
case 'hashchange':
|
||||
if (document.hidden || this.ignoreHashChangeTimeout !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
this.ignoreHashChangeTimeout = setTimeout(() => {
|
||||
this.ignoreHashChangeTimeout = null;
|
||||
}, HASH_CHANGE_DEBOUNCE);
|
||||
|
||||
// If a dialog is showing, cancel the dialog
|
||||
for (var dialog of this.apps.dialogs.concat(this.pages.dialogs)) {
|
||||
if (!dialog.opened) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dialog.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in edit mode, cancel edit mode
|
||||
if (this.apps.editMode) {
|
||||
this.apps.exitEditMode();
|
||||
return;
|
||||
}
|
||||
if (this.pages.editMode) {
|
||||
this.pages.exitEditMode();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're on the pages panel and scrolled to the top, show apps
|
||||
if (!this.appsVisible && this.pages.scrollable.scrollTop === 0) {
|
||||
this.panels.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, scroll the visible panel to the top
|
||||
var visiblePanel = this.appsVisible ?
|
||||
this.apps.scrollable : this.pages.scrollable;
|
||||
visiblePanel.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
|
||||
case 'localized':
|
||||
this.updatePanelIndicator();
|
||||
return;
|
||||
|
||||
// Exit edit mode if the user switches away from the home screen (e.g.
|
||||
// by locking the phone).
|
||||
case 'visibilitychange':
|
||||
if (document.hidden) {
|
||||
this.apps.exitEditMode();
|
||||
this.pages.exitEditMode();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.AppWindow = AppWindow;
|
||||
|
||||
}(window));
|
||||
18
gaia/apps/homescreen/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "homescreen",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["apps/homescreen/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/homescreen"
|
||||
}
|
||||
}
|
||||
16
gaia/apps/homescreen/locales/homescreen.en-US.properties
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
stop-download-body=Do you want to stop downloading {{name}}?
|
||||
stop-download-action=Stop
|
||||
|
||||
resume-download-body=Do you want to download {{name}}?
|
||||
resume-download-action=Download
|
||||
|
||||
cancel-action=Cancel
|
||||
|
||||
apps-panel.ariaLabel=Applications
|
||||
pages-panel.ariaLabel=Pinned Pages
|
||||
page-indicator-text.ariaValueText = Page {{currentPage}} of {{totalPages}}
|
||||
|
||||
no-pinned-pages-header=No Pinned Pages yet.
|
||||
no-pinned-pages-body=Use your browser to pin any web page to this screen.
|
||||
|
||||
edit-done=Done
|
||||
74
gaia/apps/homescreen/manifest.webapp
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"version": "2.7",
|
||||
"name": "Default Home Screen",
|
||||
"description": "Gaia home screen.",
|
||||
"launch_path": "/index.html",
|
||||
"type": "privileged",
|
||||
"role": "homescreen",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"icons": {
|
||||
"16": "/icons/16.png",
|
||||
"32": "/icons/32.png",
|
||||
"48": "/icons/48.png",
|
||||
"60": "/icons/60.png",
|
||||
"64": "/icons/64.png",
|
||||
"90": "/icons/90.png",
|
||||
"128": "/icons/128.png",
|
||||
"256": "/icons/256.png",
|
||||
"512": "/icons/512.png"
|
||||
},
|
||||
"permissions": {
|
||||
"homescreen-webapps-manage": {},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {},
|
||||
"systemXHR": {}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "الشاشة الرئيسية",
|
||||
"description": "Gaia الشاشة الرئيسية"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Default Home Screen",
|
||||
"description": "Gaia Home Screen."
|
||||
},
|
||||
"fr": {
|
||||
"name": "Écran d’accueil",
|
||||
"description": "Écran d’accueil Gaia"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "主畫面",
|
||||
"description": "Gaia 主畫面"
|
||||
}
|
||||
},
|
||||
"datastores-access": {
|
||||
"bookmarks_store": {
|
||||
"readonly": true,
|
||||
"description": "View bookmarks."
|
||||
},
|
||||
"places": {
|
||||
"readonly": false,
|
||||
"description": "View and modify data about browsing history."
|
||||
},
|
||||
"icons": {
|
||||
"readonly": false,
|
||||
"description": "Store and retrieve cached website icons."
|
||||
},
|
||||
"homescreen_settings": {
|
||||
"readonly": false,
|
||||
"description": "Read and write home screen settings."
|
||||
},
|
||||
"vertical_preferences_store": {
|
||||
"readonly": true,
|
||||
"description": "Import settings from old home screen."
|
||||
},
|
||||
"verticalhome_items_migration": {
|
||||
"readonly": true,
|
||||
"description": "Import settings from old home screen."
|
||||
}
|
||||
},
|
||||
"orientation": "default"
|
||||
}
|
||||
10
gaia/apps/homescreen/package.json
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"webide": {
|
||||
"prepackage": {
|
||||
"command": "make",
|
||||
"env": ["APP=homescreen"],
|
||||
"cwd": "../.."
|
||||
},
|
||||
"packageDir": "../../build_stage/homescreen/"
|
||||
}
|
||||
}
|
||||
169
gaia/apps/homescreen/style/apps-panel.css
Executable file
|
|
@ -0,0 +1,169 @@
|
|||
#apps-panel > .scrollable {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
transition: transform 0.2s;
|
||||
padding-top: 1.5vw;
|
||||
}
|
||||
|
||||
#apps-panel > .scrollable.snapping {
|
||||
scroll-snap-type-y: mandatory;
|
||||
}
|
||||
|
||||
/* This weird arrangement of constant sizes, transforms and containers is so
|
||||
* the gaia-container can animate fully between small and not-small states.
|
||||
* Without this animation, the container would be unnecessary and only the
|
||||
* width would need to be specified.
|
||||
*/
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 32vw;
|
||||
height: calc(32vw + 1.9rem);
|
||||
}
|
||||
|
||||
gaia-app-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 32vw;
|
||||
box-sizing: border-box;
|
||||
border: 0.8rem solid transparent;
|
||||
outline: 0;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
color: white;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 0 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
/* Small icons are scaled down 0.75x, so apply the reverse transforms to the
|
||||
* font so that it conforms to spec.
|
||||
*/
|
||||
#apps.small gaia-app-icon {
|
||||
font-size: calc(1.2rem / 0.75);
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 0 calc(0.1rem / 0.75) calc(0.3rem / 0.75);
|
||||
}
|
||||
|
||||
gaia-app-icon.launching {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.edit-mode gaia-app-icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.edit-mode gaia-app-icon.selected {
|
||||
transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#apps.loading {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#apps.small > .gaia-container-child > .icon-container {
|
||||
width: 24vw;
|
||||
height: calc(24vw + 1.9rem);
|
||||
}
|
||||
|
||||
#apps > .gaia-container-child.dragging > .icon-container > gaia-app-icon {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#apps.small > .gaia-container-child > .icon-container > gaia-app-icon {
|
||||
transform: scale(0.75) translate(-4vw, -4vw);
|
||||
}
|
||||
|
||||
#apps.small > .gaia-container-child.dragging > .icon-container > gaia-app-icon {
|
||||
transform: scale(0.8) translate(-3.2vw, -3.2vw);
|
||||
}
|
||||
|
||||
.dragging:not(.autoscroll) #apps > .gaia-container-child > .icon-container {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
#apps > .gaia-container-child > .icon-container.hover-before {
|
||||
transform: translateX(3.5rem);
|
||||
}
|
||||
|
||||
#apps > .gaia-container-child > .icon-container.hover-after {
|
||||
transform: translateX(-3.5rem);
|
||||
}
|
||||
|
||||
#apps > .gaia-container-child.added gaia-app-icon {
|
||||
animation-name: icon-added;
|
||||
animation-duration: 0.4s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#apps.small > .gaia-container-child.added gaia-app-icon {
|
||||
animation-name: small-icon-added;
|
||||
}
|
||||
|
||||
@keyframes icon-added {
|
||||
0% { opacity: 0; transform: scale(0.5); }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes small-icon-added {
|
||||
0% { opacity: 0; transform: scale(0.375) translate(-6vw, -6vw); }
|
||||
100% { opacity: 1; transform: scale(0.75) translate(-4vw, -4vw); }
|
||||
}
|
||||
|
||||
#apps > .gaia-container-child.removed gaia-app-icon {
|
||||
animation-name: icon-removed;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
#apps.small > .gaia-container-child.removed gaia-app-icon {
|
||||
animation-name: small-icon-removed;
|
||||
}
|
||||
|
||||
@keyframes icon-removed {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
100% { transform: scale(0.5); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes small-icon-removed {
|
||||
0% { transform: scale(0.75) translate(-4vw, -4vw); opacity: 1; }
|
||||
100% { transform: scale(0.375) translate(-6vw, -6vw); opacity: 0; }
|
||||
}
|
||||
|
||||
gaia-app-icon.uneditable {
|
||||
animation-name: icon-uneditable;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
#apps.small gaia-app-icon.uneditable {
|
||||
animation-name: small-icon-uneditable;
|
||||
}
|
||||
|
||||
@keyframes icon-uneditable {
|
||||
0% { transform: rotateZ(0deg); }
|
||||
10% { transform: rotateZ(-15deg); }
|
||||
20% { transform: rotateZ(15deg); }
|
||||
30% { transform: rotateZ(-15deg); }
|
||||
40% { transform: rotateZ(15deg); }
|
||||
50% { transform: rotateZ(-15deg); }
|
||||
60% { transform: rotateZ(15deg); }
|
||||
70% { transform: rotateZ(-15deg); }
|
||||
80% { transform: rotateZ(15deg); }
|
||||
90% { transform: rotateZ(-15deg); }
|
||||
100% { transform: rotateZ(0deg); }
|
||||
}
|
||||
|
||||
@keyframes small-icon-uneditable {
|
||||
0% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(0deg); }
|
||||
10% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(-15deg); }
|
||||
20% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(15deg); }
|
||||
30% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(-15deg); }
|
||||
40% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(15deg); }
|
||||
50% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(-15deg); }
|
||||
60% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(15deg); }
|
||||
70% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(-15deg); }
|
||||
80% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(15deg); }
|
||||
90% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(-15deg); }
|
||||
100% { transform: scale(0.75) translate(-4vw, -4vw) rotateZ(0deg); }
|
||||
}
|
||||
33
gaia/apps/homescreen/style/grid.css
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
gaia-container {
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-content: flex-start;
|
||||
transition: opacity 0.2s;
|
||||
box-sizing: padding-box;
|
||||
padding: 0 0.6rem;
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
|
||||
.snapping gaia-container {
|
||||
background: linear-gradient(to bottom, transparent,
|
||||
transparent 50%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.15));
|
||||
background-size: 100% 200%;
|
||||
background-repeat: repeat-y;
|
||||
}
|
||||
|
||||
gaia-container:not(.loading) > .gaia-container-child:not(.added):not(.dragging) {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child.dragging {
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
gaia-container > .gaia-container-child:not(.added):not(.removed) > * {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
1
gaia/apps/homescreen/style/images/delete_bottom.svg
Executable file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45"><path d="M11.2 17.5l2 20.2c.1 1.5 1.4 2.6 2.9 2.6h13.1c1.4 0 2.7-1.2 2.9-2.6L34 17.5H11.2z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 174 B |
1
gaia/apps/homescreen/style/images/delete_lid.svg
Executable file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45"><path d="M31.9 9.6h-2v-1c0-2.1-1.7-3.9-3.9-3.9h-7.1c-2.1 0-3.9 1.7-3.9 3.9v1h-2c-2.2 0-4 1.8-4 4v2.7h26.8v-2.7c.1-2.2-1.7-4-3.9-4zm-14.7-.9c0-1.1.9-2 2-2h6.6c1.1 0 2 .9 2 2v.8H17.2v-.8z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 269 B |
BIN
gaia/apps/homescreen/style/images/empty_home_image.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
gaia/apps/homescreen/style/images/empty_home_image@1.5x.png
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
gaia/apps/homescreen/style/images/empty_home_image@2.25x.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
gaia/apps/homescreen/style/images/empty_home_image@2x.png
Executable file
|
After Width: | Height: | Size: 33 KiB |
1
gaia/apps/homescreen/style/images/rename.svg
Executable file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19"><path d="M18.56 4.11L4.87 17.8.997 13.922 14.684.235c.545-.543 1.853-.117 2.923.953s1.496 2.378.952 2.922zM3.7 18.786H.008v-3.692L3.7 18.786z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 225 B |
148
gaia/apps/homescreen/style/pages-panel.css
Executable file
|
|
@ -0,0 +1,148 @@
|
|||
#pages-panel {
|
||||
font-size: 1.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#pages-panel > .scrollable {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#empty-pages {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-left: 2.2rem solid transparent;
|
||||
border-right: 2.2rem solid transparent;
|
||||
overflow: hidden;
|
||||
|
||||
color: white;
|
||||
font-weight: 400;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 0 0.1rem 0.3rem;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
|
||||
transition: visibility 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
#empty-pages h1 {
|
||||
font-size: 1.6rem;
|
||||
margin: 2.333rem 0;
|
||||
}
|
||||
|
||||
#empty-pages p {
|
||||
font-size: 1.4rem;
|
||||
margin: 0 2.333rem 1.266rem;
|
||||
}
|
||||
|
||||
#empty-pages img {
|
||||
width: 100%;
|
||||
margin-bottom: 1.8666rem;
|
||||
}
|
||||
|
||||
div#boxes {
|
||||
position: relative;
|
||||
height: calc(15rem + 15rem + 1.0666rem);
|
||||
}
|
||||
|
||||
div#boxes > div {
|
||||
position: absolute;
|
||||
width: calc(50% - 1.0666rem / 2);
|
||||
height: 15rem;
|
||||
border: 0.2rem dashed rgba(255, 255, 255, 0.8);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Pseudo-element to get a shadow */
|
||||
div#boxes > div:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: -0.1rem;
|
||||
right: -0.2rem;
|
||||
bottom: -0.3rem;
|
||||
left: -0.2rem;
|
||||
border: 0.2rem dashed rgba(0, 0, 0, 0.2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
div#boxes > div:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div#boxes > div:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
div#boxes > div:nth-child(3) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div#boxes > div:nth-child(4) {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#pages-panel:not(.empty) #empty-pages {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#pages {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
min-height: 100%;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
#pages gaia-pin-card {
|
||||
display: block;
|
||||
width: calc(50vw - 1.5vw - 0.75rem);
|
||||
height: 16.5rem;
|
||||
padding: 0 0 1.5rem 1.5rem;
|
||||
outline: 0;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.edit-mode #pages gaia-pin-card {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.edit-mode #pages gaia-pin-card.selected {
|
||||
transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#pages gaia-pin-card.unpinning {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#pages > .gaia-container-child.dragging > gaia-pin-card {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#pages > .gaia-container-child.added gaia-pin-card {
|
||||
animation-name: card-added;
|
||||
animation-duration: 0.4s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes card-added {
|
||||
0% { opacity: 0; transform: scale(0.8); }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
#pages > .gaia-container-child.removed gaia-pin-card {
|
||||
animation-name: card-removed;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes card-removed {
|
||||
0% { opacity: 0.8; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
212
gaia/apps/homescreen/style/window.css
Executable file
|
|
@ -0,0 +1,212 @@
|
|||
html {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#page-indicator-header {
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#page-indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.dialog-active #page-indicator,
|
||||
.edit-mode #page-indicator {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#page-indicator span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
margin: 0 0.25rem 0.5rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#page-indicator span:before,
|
||||
#page-indicator span:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
#page-indicator span:before {
|
||||
opacity: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#page-indicator span:after {
|
||||
opacity: 1;
|
||||
border: 0.1rem solid white;
|
||||
}
|
||||
|
||||
#page-indicator span.active:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#page-indicator span.active:after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#panels {
|
||||
height: calc(100vh - 1.5rem);
|
||||
overflow-y: hidden;
|
||||
scroll-snap-type-x: mandatory;
|
||||
scroll-snap-points-x: repeat(100vw);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dragging #panels,
|
||||
.edit-mode #panels {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#panels > * {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#shadow {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
z-index: 1;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
|
||||
width: 100%;
|
||||
height: 0.8rem;
|
||||
transition: visibility 0.3s, opacity 0.3s;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#shadow.visible {
|
||||
opacity: 0.5;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.dialog-active #shadow.visible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#bottombar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 5.5rem;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
transition: transform 0.2s;
|
||||
transform: translateY(6.5rem);
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.edit-mode #bottombar {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#bottombar > * {
|
||||
display: inline-block;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#bottombar > *.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#remove {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
background: no-repeat center center url('images/delete_bottom.svg');
|
||||
background-size: 3.0rem 3.0rem;
|
||||
}
|
||||
|
||||
#remove::after {
|
||||
display: block;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: no-repeat center center url('images/delete_lid.svg');
|
||||
background-size: 3.0rem 3.0rem;
|
||||
transform-origin: calc(50% + 1.75rem) calc(50% + 0.8rem);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
#remove:active:after {
|
||||
transform: rotateZ(30deg);
|
||||
}
|
||||
|
||||
#rename {
|
||||
flex: 1;
|
||||
background: no-repeat center center url('images/rename.svg');
|
||||
background-size: 2.0rem 2.0rem;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
#rename:active {
|
||||
transform: rotateZ(-45deg);
|
||||
}
|
||||
|
||||
#done {
|
||||
display: inline-flex;
|
||||
flex: 1;
|
||||
opacity: 1;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: white;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#done:active {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
.edit-underlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.10);
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.edit-mode .edit-underlay {
|
||||
opacity: 1;
|
||||
}
|
||||
27
gaia/apps/homescreen/test/build/integration/homescreen_test.js
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true, mocha: true */
|
||||
/* global suiteSetup */
|
||||
|
||||
var helper = require('helper');
|
||||
|
||||
suite('Homescreen tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
test('APP=homescreen make', function(done) {
|
||||
helper.exec('APP=homescreen make', function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var zipPath = process.cwd() +
|
||||
'/profile/webapps/homescreen.gaiamobile.org/application.zip';
|
||||
var checkList = ['manifest.webapp'];
|
||||
|
||||
checkList.forEach(function(filePath) {
|
||||
helper.checkFilePathInZip(zipPath, filePath);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
19
gaia/apps/homescreen/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"__dirname",
|
||||
"module",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished",
|
||||
"require",
|
||||
"define",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
65
gaia/apps/homescreen/test/marionette/app_appcache_pending_test.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Appcache installed download', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/appcache';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('shows spinner while downloading', function() {
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure appcache path is delayed
|
||||
server.cork(server.manifest.appcache_path);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// wait until we see the nice spinner thing
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.manifest.appcache_path);
|
||||
// wait until it is no longer loading
|
||||
client.waitFor(function() {
|
||||
return !home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// ensure the app launches!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
38
gaia/apps/homescreen/test/marionette/app_blacklist_test.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - App Blacklist', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var home, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
|
||||
// Fail finding elements quickly.
|
||||
client.setSearchTimeout(20);
|
||||
});
|
||||
|
||||
function lookForIcon(manifestURL) {
|
||||
try {
|
||||
home.getIcon(manifestURL);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
test('app does not appear on home screen if in blacklist', function() {
|
||||
// Ensure that a normal icon is there.
|
||||
var calendarApp = lookForIcon('app://calendar.gaiamobile.org');
|
||||
assert.ok(calendarApp);
|
||||
});
|
||||
|
||||
});
|
||||
54
gaia/apps/homescreen/test/marionette/app_default_order_test.js
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Default app order', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var home, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('Default app order is respected', function() {
|
||||
var expectedOrder = [
|
||||
'Phone',
|
||||
'Messages',
|
||||
'Contacts',
|
||||
'E-Mail',
|
||||
'Browser',
|
||||
'Camera',
|
||||
'Gallery',
|
||||
'Music',
|
||||
'Video',
|
||||
'Marketplace',
|
||||
'Calendar',
|
||||
'Clock',
|
||||
'Settings',
|
||||
'FM Radio',
|
||||
'BuddyUp',
|
||||
'Bugzilla Lite',
|
||||
'Facebook',
|
||||
'Twitter',
|
||||
'Notes',
|
||||
'Calculator',
|
||||
'Usage'
|
||||
];
|
||||
var length = expectedOrder.length;
|
||||
|
||||
var iconNames = home.visibleIcons.map(function(icon) {
|
||||
return home.getIconText(icon);
|
||||
}).slice(0, length);
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
assert.equal(iconNames[i], expectedOrder[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
60
gaia/apps/homescreen/test/marionette/app_hosted_failed_icon_fetch_test.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app failed icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('fallback to default icon', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure the icon fails to download!
|
||||
server.fail(iconURL);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure the default icon is shown
|
||||
home.waitForIconImageUrl(icon, 'default');
|
||||
|
||||
// ensure the icon can be launched!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
44
gaia/apps/homescreen/test/marionette/app_hosted_install_test.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted App Install', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('install app', function() {
|
||||
client.switchToFrame();
|
||||
appInstall.install(server.manifestURL);
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
home.launchIcon(home.getIcon(server.manifestURL));
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
113
gaia/apps/homescreen/test/marionette/app_hosted_retry_icon_fetch_test.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app failed icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var actions, home, system, appInstall;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('shows icon after a restart', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.scrollIconToCenter(icon);
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// stop the download
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// Restart the download
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// resume the download from the ui
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
|
||||
// wait until we are showing our desired icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
});
|
||||
|
||||
test('fallback to default icon when icon fails', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure the icon fails to download!
|
||||
server.fail(iconURL);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure the default icon is shown
|
||||
home.waitForIconImageUrl(icon, 'default');
|
||||
|
||||
server.unfail(iconURL);
|
||||
|
||||
// XXX: We don't have real network access but we can sorta emulate it by
|
||||
// failing then switching back to an online state.
|
||||
client.executeScript(function() {
|
||||
window.dispatchEvent(new CustomEvent('online'));
|
||||
});
|
||||
|
||||
// wait until we are showing our desired icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// ensure the icon can be launched!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
86
gaia/apps/homescreen/test/marionette/app_hosted_use_cached_icon_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app cached icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('fallback to default icon', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure it is cached
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
var id = home.getIconId(icon);
|
||||
client.waitFor(function() {
|
||||
var metadata = client.executeAsyncScript(function() {
|
||||
window.wrappedJSObject.appWindow.apps.metadata.getAll().then(
|
||||
marionetteScriptFinished);
|
||||
});
|
||||
|
||||
for (var i = 0, iLen = metadata.length; i < iLen; i++) {
|
||||
if (metadata[i].id.startsWith(id) &&
|
||||
typeof metadata[i].icon !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// ensure http fails so we use the cached icon
|
||||
server.fail(iconURL);
|
||||
|
||||
// Reload the homescreen
|
||||
home.restart();
|
||||
icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// check for the cached icon...
|
||||
home.waitForIconImageUrl(icon, 'user-set');
|
||||
|
||||
// allow the request to succeed
|
||||
server.unfail(iconURL);
|
||||
|
||||
// trigger the download of a new icon
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.dispatchEvent(new CustomEvent('online'));
|
||||
});
|
||||
|
||||
// now shows the freshly redownloaded icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
});
|
||||
|
||||
});
|
||||
66
gaia/apps/homescreen/test/marionette/app_order_test.js
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - App order', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var actions, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('App order is retained after restart', function() {
|
||||
// Drag icon to a different place
|
||||
var icons = home.visibleIcons;
|
||||
var location1 = icons[0].location();
|
||||
var location2 = icons[1].location();
|
||||
actions.wait(0.5).press(icons[0]).wait(0.5).
|
||||
move(icons[1]).release().wait(0.5).perform();
|
||||
|
||||
client.waitFor(function() {
|
||||
return icons[0].location().x === location2.x &&
|
||||
icons[1].location().x === location1.x;
|
||||
});
|
||||
|
||||
// Wait for the icon order to be saved
|
||||
var ids = home.getIconIdentifiers();
|
||||
var numIcons = ids.length;
|
||||
client.waitFor(function() {
|
||||
var order = client.executeAsyncScript(function() {
|
||||
window.wrappedJSObject.appWindow.apps.metadata.getAll().then(
|
||||
marionetteScriptFinished);
|
||||
});
|
||||
|
||||
// The order array is stored in order, but also contains non-visible
|
||||
// icons, so just skip unknown entries.
|
||||
var correctlyPlacedIcons = 0;
|
||||
for (var i = 0, iLen = order.length; i < iLen; i++) {
|
||||
if (order[i].id.startsWith(ids[correctlyPlacedIcons])) {
|
||||
++ correctlyPlacedIcons;
|
||||
}
|
||||
}
|
||||
return correctlyPlacedIcons === numIcons;
|
||||
});
|
||||
|
||||
// Test that icon ordering is retained after a restart
|
||||
home.restart();
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.visibleIcons.length === numIcons;
|
||||
});
|
||||
|
||||
var newIds = home.getIconIdentifiers();
|
||||
for (var i = 0, iLen = ids.length; i < iLen; i++) {
|
||||
assert.equal(ids[i], newIds[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
59
gaia/apps/homescreen/test/marionette/app_packaged_fail_test.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Failed Download', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('failed state then retry and launch', function() {
|
||||
client.switchToFrame();
|
||||
|
||||
server.fail(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
appInstall.dismissToast();
|
||||
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.waitForIconImageUrl(icon, 'failed');
|
||||
|
||||
server.unfail(server.applicationZipUri);
|
||||
|
||||
icon.tap();
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
45
gaia/apps/homescreen/test/marionette/app_packaged_install_test.js
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Install', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('install app', function() {
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
61
gaia/apps/homescreen/test/marionette/app_packaged_pending_test.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('app loading spinner', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
// wait until the icon is spinning!
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// wait until it is no longer loading
|
||||
client.waitFor(function() {
|
||||
return !home.iconIsLoading(icon);
|
||||
});
|
||||
});
|
||||
});
|
||||
63
gaia/apps/homescreen/test/marionette/app_packaged_pending_update_test.js
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
|
||||
// install the app
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('update an installed app', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Stage the update
|
||||
appInstall.stageUpdate(server.packageManifestURL);
|
||||
|
||||
// ensure we can still launch an app in the older version
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
93
gaia/apps/homescreen/test/marionette/app_packaged_resume_test.js
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Resuming Downloads', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server, actions, home, system, appInstall;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('failed state then retry and launch', function() {
|
||||
client.switchToFrame();
|
||||
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
// Get the icon
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// Scroll so the icon is in the middle of the screen and won't be
|
||||
// obscured by the system toast
|
||||
home.scrollIconToCenter(icon);
|
||||
|
||||
// pause the download
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// resume the download
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// pause it again!
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// uncork so next resume works...
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// finally download the entire app
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// now it should work!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
101
gaia/apps/homescreen/test/marionette/app_packaged_resume_update_test.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Resume Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var actions, home, system, appInstall;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
// install the app
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('resume update', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// Scroll so the icon is in the middle of the screen and won't be
|
||||
// obscured by the system toast
|
||||
home.scrollIconToCenter(icon);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Cork the zip so we can see the loading spinner.
|
||||
server.cork('/app.zip');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Ensure we have an update.
|
||||
appInstall.update(server.packageManifestURL);
|
||||
|
||||
// Ensure we see the loading signs...
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// pause the update
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// resume the update
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// Wait for the download to be complete.
|
||||
server.uncork('/app.zip');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// verify the app was really updated
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'updatedwow');
|
||||
});
|
||||
});
|
||||
|
||||
59
gaia/apps/homescreen/test/marionette/app_packaged_role_hidden_test.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/role_homescreen';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('app is displayed until determined role=homescreen', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
// wait until the icon is spinning!
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// wait until the icon is hidden
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
});
|
||||
});
|
||||
93
gaia/apps/homescreen/test/marionette/app_packaged_update_test.js
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
// Launch the homescreen first, then go to the system app.
|
||||
// Make sure we do this before installing an application.
|
||||
home.waitForLaunch();
|
||||
client.switchToFrame();
|
||||
|
||||
// install the app with a broken icon
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('update an installed app', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Prepare to screenshot
|
||||
var iconImage = home.getIconImage(icon);
|
||||
var iconTitle = home.getIconText(icon);
|
||||
|
||||
// Cork the zip so we can see the loading spinner.
|
||||
server.cork('/app.zip');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Ensure we have an update.
|
||||
appInstall.update(server.packageManifestURL);
|
||||
|
||||
// Ensure we see the loading signs...
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// Wait for the download to be complete.
|
||||
server.uncork('/app.zip');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// See bug 826555 for rationale for the title not changing.
|
||||
assert.equal(
|
||||
home.getIconText(icon),
|
||||
iconTitle,
|
||||
'app name should not be updated'
|
||||
);
|
||||
|
||||
// Make sure the icon changed
|
||||
assert.notEqual(
|
||||
home.getIconImage(icon),
|
||||
iconImage,
|
||||
'app icon should have updated');
|
||||
|
||||
// verify the app was really updated
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'updatedwow');
|
||||
});
|
||||
});
|
||||