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.
23
gaia/tv_apps/app-deck/bower.json
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "app-deck",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/tv_apps/app-deck",
|
||||
"description": "Smart Screen Apps Deck",
|
||||
"main": "index.html",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"evt": "fxos-components/evt#~0.4.0",
|
||||
"smart-button": "fxos-components/smart-button",
|
||||
"smart-bubbles": "smart-components/smart-bubbles",
|
||||
"smart-dialog": "smart-components/smart-dialog",
|
||||
"smart-modal-dialog": "smart-components/smart-modal-dialog"
|
||||
}
|
||||
}
|
||||
28
gaia/tv_apps/app-deck/bower_components/evt/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"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"
|
||||
],
|
||||
"_release": "0.4.0",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.4.0",
|
||||
"commit": "3593dd6b4dd19f683d00fd0eaf2581c11a128da6"
|
||||
},
|
||||
"_source": "git://github.com/fxos-components/evt.git",
|
||||
"_target": "~0.4.0",
|
||||
"_originalSource": "fxos-components/evt"
|
||||
}
|
||||
19
gaia/tv_apps/app-deck/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/tv_apps/app-deck/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;
|
||||
}
|
||||
|
||||
})();
|
||||
30
gaia/tv_apps/app-deck/bower_components/smart-bubbles/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "smart-bubbles",
|
||||
"homepage": "https://github.com/smart-components/smart-bubbles",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"gulpfile.js",
|
||||
"package.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"smart-button": "smart-components/smart-button"
|
||||
},
|
||||
"_release": "5c08ef5d84",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "5c08ef5d84b0bd1d8252600cfc744aa7869e9f92"
|
||||
},
|
||||
"_source": "git://github.com/smart-components/smart-bubbles.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "smart-components/smart-bubbles"
|
||||
}
|
||||
22
gaia/tv_apps/app-deck/bower_components/smart-bubbles/bower.json
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "smart-bubbles",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/smart-components/smart-bubbles",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"gulpfile.js",
|
||||
"package.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"smart-button": "smart-components/smart-button"
|
||||
}
|
||||
}
|
||||
83
gaia/tv_apps/app-deck/bower_components/smart-bubbles/script.js
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
window.SmartBubbles = (function(win) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_INTERVAL = 100;
|
||||
|
||||
// Extend from the HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
proto.fireEvent = function(event, detail) {
|
||||
var evtObject = new CustomEvent(event, {
|
||||
bubbles: false,
|
||||
detail: detail || this
|
||||
});
|
||||
this.dispatchEvent(evtObject);
|
||||
};
|
||||
|
||||
proto.handleEvent = function(evt) {
|
||||
switch(evt.type) {
|
||||
case 'animationend':
|
||||
this.fireEvent('item-bubbled', ++this._finishedAnimationCount);
|
||||
if (this._targetAnimationCount === this._finishedAnimationCount) {
|
||||
this.playing = false;
|
||||
[].forEach.call(this._elements, function(elem) {
|
||||
elem.classList.remove('smart-bubble-playing');
|
||||
// XXX:Usually we use <smart-button> within container of
|
||||
// smart-bubbles. If we remove style of smart-bubble-playing,
|
||||
// Gecko would apply original style of smart-button instead and it
|
||||
// has transition property too. This results in another transition
|
||||
// happens on <smart-button> while we thought we'd just stop
|
||||
// animation. That is why we need to add 'smart-bubble-stopped' here
|
||||
// to prevent unexpected animation from happening.
|
||||
elem.classList.add('smart-bubble-stopped');
|
||||
// use getComputedStyle to force flushing re-style
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.classList.remove('smart-bubble-stopped');
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.style.animationDelay = '';
|
||||
}.bind(this));
|
||||
this._elements = null;
|
||||
this.fireEvent('all-items-bubbled');
|
||||
}
|
||||
evt.currentTarget.removeEventListener('animationend', this);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
proto.play = function(elements) {
|
||||
this.stopImmediately();
|
||||
this._elements = elements;
|
||||
this._finishedAnimationCount = 0;
|
||||
this._targetAnimationCount = 0;
|
||||
var interval = parseInt(this.getAttribute('interval'), 10) ||
|
||||
DEFAULT_INTERVAL;
|
||||
[].forEach.call(elements, function(elem) {
|
||||
this._targetAnimationCount++;
|
||||
var delay = this._targetAnimationCount * interval / 1000;
|
||||
elem.classList.add('smart-bubble-playing');
|
||||
elem.style.animationDelay = delay + 's';
|
||||
elem.addEventListener('animationend', this);
|
||||
}.bind(this));
|
||||
this.playing = true;
|
||||
};
|
||||
|
||||
proto.stopImmediately = function() {
|
||||
if (!this.playing || !this._elements) {
|
||||
return;
|
||||
}
|
||||
[].forEach.call(this._elements, function(elem) {
|
||||
elem.classList.remove('smart-bubble-playing');
|
||||
elem.classList.add('smart-bubble-stopped');
|
||||
elem.removeEventListener('animationend', this);
|
||||
// use getComputedStyle to force flushing re-style
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.classList.remove('smart-bubble-stopped');
|
||||
}.bind(this));
|
||||
this._elements = null;
|
||||
this.playing = false;
|
||||
this.fireEvent('all-items-stopped');
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('smart-bubbles', { prototype: proto });
|
||||
})(window);
|
||||
1
gaia/tv_apps/app-deck/bower_components/smart-bubbles/script.min.js
vendored
Executable file
|
|
@ -0,0 +1 @@
|
|||
window.SmartBubbles=function(t){"use strict";var e=100,i=Object.create(HTMLElement.prototype);return i.fireEvent=function(t,e){var i=new CustomEvent(t,{bubbles:!1,detail:e||this});this.dispatchEvent(i)},i.handleEvent=function(t){switch(t.type){case"animationend":this.fireEvent("item-bubbled",++this._finishedAnimationCount),this._targetAnimationCount===this._finishedAnimationCount&&(this.playing=!1,[].forEach.call(this._elements,function(t){t.classList.remove("smart-bubble-playing"),t.classList.add("smart-bubble-stopped"),window.getComputedStyle(t).animationName,t.classList.remove("smart-bubble-stopped"),window.getComputedStyle(t).animationName,t.style.animationDelay=""}.bind(this)),this._elements=null,this.fireEvent("all-items-bubbled")),t.currentTarget.removeEventListener("animationend",this)}},i.play=function(t){this.stopImmediately(),this._elements=t,this._finishedAnimationCount=0,this._targetAnimationCount=0;var i=parseInt(this.getAttribute("interval"),10)||e;[].forEach.call(t,function(t){this._targetAnimationCount++;var e=this._targetAnimationCount*i/1e3;t.classList.add("smart-bubble-playing"),t.style.animationDelay=e+"s",t.addEventListener("animationend",this)}.bind(this)),this.playing=!0},i.stopImmediately=function(){this.playing&&this._elements&&([].forEach.call(this._elements,function(t){t.classList.remove("smart-bubble-playing"),t.classList.add("smart-bubble-stopped"),t.removeEventListener("animationend",this),window.getComputedStyle(t).animationName,t.classList.remove("smart-bubble-stopped")}.bind(this)),this._elements=null,this.playing=!1,this.fireEvent("all-items-stopped"))},document.registerElement("smart-bubbles",{prototype:i})}(window);
|
||||
83
gaia/tv_apps/app-deck/bower_components/smart-bubbles/src/script.js
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
window.SmartBubbles = (function(win) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_INTERVAL = 100;
|
||||
|
||||
// Extend from the HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
proto.fireEvent = function(event, detail) {
|
||||
var evtObject = new CustomEvent(event, {
|
||||
bubbles: false,
|
||||
detail: detail || this
|
||||
});
|
||||
this.dispatchEvent(evtObject);
|
||||
};
|
||||
|
||||
proto.handleEvent = function(evt) {
|
||||
switch(evt.type) {
|
||||
case 'animationend':
|
||||
this.fireEvent('item-bubbled', ++this._finishedAnimationCount);
|
||||
if (this._targetAnimationCount === this._finishedAnimationCount) {
|
||||
this.playing = false;
|
||||
[].forEach.call(this._elements, function(elem) {
|
||||
elem.classList.remove('smart-bubble-playing');
|
||||
// XXX:Usually we use <smart-button> within container of
|
||||
// smart-bubbles. If we remove style of smart-bubble-playing,
|
||||
// Gecko would apply original style of smart-button instead and it
|
||||
// has transition property too. This results in another transition
|
||||
// happens on <smart-button> while we thought we'd just stop
|
||||
// animation. That is why we need to add 'smart-bubble-stopped' here
|
||||
// to prevent unexpected animation from happening.
|
||||
elem.classList.add('smart-bubble-stopped');
|
||||
// use getComputedStyle to force flushing re-style
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.classList.remove('smart-bubble-stopped');
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.style.animationDelay = '';
|
||||
}.bind(this));
|
||||
this._elements = null;
|
||||
this.fireEvent('all-items-bubbled');
|
||||
}
|
||||
evt.currentTarget.removeEventListener('animationend', this);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
proto.play = function(elements) {
|
||||
this.stopImmediately();
|
||||
this._elements = elements;
|
||||
this._finishedAnimationCount = 0;
|
||||
this._targetAnimationCount = 0;
|
||||
var interval = parseInt(this.getAttribute('interval'), 10) ||
|
||||
DEFAULT_INTERVAL;
|
||||
[].forEach.call(elements, function(elem) {
|
||||
this._targetAnimationCount++;
|
||||
var delay = this._targetAnimationCount * interval / 1000;
|
||||
elem.classList.add('smart-bubble-playing');
|
||||
elem.style.animationDelay = delay + 's';
|
||||
elem.addEventListener('animationend', this);
|
||||
}.bind(this));
|
||||
this.playing = true;
|
||||
};
|
||||
|
||||
proto.stopImmediately = function() {
|
||||
if (!this.playing || !this._elements) {
|
||||
return;
|
||||
}
|
||||
[].forEach.call(this._elements, function(elem) {
|
||||
elem.classList.remove('smart-bubble-playing');
|
||||
elem.classList.add('smart-bubble-stopped');
|
||||
elem.removeEventListener('animationend', this);
|
||||
// use getComputedStyle to force flushing re-style
|
||||
window.getComputedStyle(elem).animationName;
|
||||
elem.classList.remove('smart-bubble-stopped');
|
||||
}.bind(this));
|
||||
this._elements = null;
|
||||
this.playing = false;
|
||||
this.fireEvent('all-items-stopped');
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('smart-bubbles', { prototype: proto });
|
||||
})(window);
|
||||
26
gaia/tv_apps/app-deck/bower_components/smart-bubbles/src/style.css
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
*[smart-bubbles="true"] .smart-bubble-playing {
|
||||
transition-property: none;
|
||||
transform: none;
|
||||
animation: bubble-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
|
||||
}
|
||||
|
||||
*[smart-bubbles="true"][smart-bubbles-direction="down"] .smart-bubble-playing {
|
||||
animation: sinking-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
|
||||
}
|
||||
|
||||
*[smart-bubbles="true"] .smart-bubble-stopped {
|
||||
transition-property: none;
|
||||
transform: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes bubble-animation {
|
||||
0% {opacity: 0; transform: translateY(8rem);}
|
||||
50% {opacity: 1;}
|
||||
100% {transform: translateY(0);}
|
||||
}
|
||||
|
||||
@keyframes sinking-animation {
|
||||
0% {opacity: 1; transform: translateY(0);}
|
||||
100% {opacity: 0; transform: translateY(8rem);}
|
||||
}
|
||||
26
gaia/tv_apps/app-deck/bower_components/smart-bubbles/style.css
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
*[smart-bubbles="true"] .smart-bubble-playing {
|
||||
transition-property: none;
|
||||
transform: none;
|
||||
animation: bubble-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
|
||||
}
|
||||
|
||||
*[smart-bubbles="true"][smart-bubbles-direction="down"] .smart-bubble-playing {
|
||||
animation: sinking-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
|
||||
}
|
||||
|
||||
*[smart-bubbles="true"] .smart-bubble-stopped {
|
||||
transition-property: none;
|
||||
transform: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes bubble-animation {
|
||||
0% {opacity: 0; transform: translateY(8rem);}
|
||||
50% {opacity: 1;}
|
||||
100% {transform: translateY(0);}
|
||||
}
|
||||
|
||||
@keyframes sinking-animation {
|
||||
0% {opacity: 1; transform: translateY(0);}
|
||||
100% {opacity: 0; transform: translateY(8rem);}
|
||||
}
|
||||
26
gaia/tv_apps/app-deck/bower_components/smart-button/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "smart-button",
|
||||
"version": "0.0.3",
|
||||
"homepage": "https://github.com/gaia-components/smart-button",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"_release": "0.0.3",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.0.3",
|
||||
"commit": "9994b26e2acc5a2f3a505fe4ae04795fbe96d072"
|
||||
},
|
||||
"_source": "git://github.com/fxos-components/smart-button.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "fxos-components/smart-button"
|
||||
}
|
||||
17
gaia/tv_apps/app-deck/bower_components/smart-button/bower.json
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "smart-button",
|
||||
"version": "0.0.3",
|
||||
"homepage": "https://github.com/gaia-components/smart-button",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
62
gaia/tv_apps/app-deck/bower_components/smart-button/script.js
vendored
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
/* global KeyEvent */
|
||||
|
||||
window.SmartButton = (function(win) {
|
||||
// Extend from the HTMLButtonElement prototype
|
||||
var proto = Object.create(HTMLButtonElement.prototype);
|
||||
|
||||
proto.createdCallback = function() {
|
||||
this.addEventListener('mousedown', this);
|
||||
this.addEventListener('mouseup', this);
|
||||
this.addEventListener('touchstart', this);
|
||||
this.addEventListener('touchend', this);
|
||||
this.addEventListener('keydown', this);
|
||||
this.addEventListener('keyup', this);
|
||||
this.addEventListener('focus', this);
|
||||
this.addEventListener('blur', this);
|
||||
this.addEventListener('transitionend', this);
|
||||
this.tabIndex = 0;
|
||||
};
|
||||
|
||||
proto.handleEvent = function(evt) {
|
||||
switch(evt.type) {
|
||||
case 'mousedown':
|
||||
case 'touchstart':
|
||||
this.classList.add('pressed');
|
||||
break;
|
||||
case 'keydown':
|
||||
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
|
||||
this.classList.add('pressed');
|
||||
}
|
||||
break;
|
||||
case 'mouseup':
|
||||
case 'touchend':
|
||||
this.classList.remove('pressed');
|
||||
this.classList.add('released');
|
||||
break;
|
||||
case 'keyup':
|
||||
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
|
||||
this.classList.remove('pressed');
|
||||
this.classList.add('released');
|
||||
this.click();
|
||||
}
|
||||
break;
|
||||
case 'transitionend':
|
||||
if (this.classList.contains('released')) {
|
||||
this.classList.remove('released');
|
||||
}
|
||||
break;
|
||||
case 'focus':
|
||||
this.classList.add('focused');
|
||||
break;
|
||||
case 'blur':
|
||||
this.classList.remove('pressed');
|
||||
this.classList.remove('released');
|
||||
this.classList.remove('focused');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('smart-button', { prototype: proto });
|
||||
})(window);
|
||||
498
gaia/tv_apps/app-deck/bower_components/smart-button/style.css
vendored
Executable file
|
|
@ -0,0 +1,498 @@
|
|||
/**
|
||||
* Button state:
|
||||
* 1. normal
|
||||
* In this state, we don't add any class.
|
||||
* 2. focused (class name: focused)
|
||||
* The focused state may be triggered by keyboard or touch/mouse.
|
||||
* 3. pressed (class name: pressed)
|
||||
* This state is triggered by user pressed confirm button, mousedown,
|
||||
* and touchdown.
|
||||
* 4. released (class name: released)
|
||||
* This state is only for changing duration of transition. We enter this
|
||||
* state when user release the button. After transitionend, we goes back
|
||||
* to focused state.
|
||||
**/
|
||||
smart-button {
|
||||
font-family: "Fira Sans";
|
||||
display: inline-block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
/* we need to set border to none to remove the button border */
|
||||
border: none;
|
||||
/* the radius is still make button as round even if we don't have border */
|
||||
border-radius: 50%;
|
||||
/* 50% is not a real circle because of rounding, we have to use a value larger
|
||||
than 50% */
|
||||
-moz-outline-radius: 60%;
|
||||
outline: transparent 0 solid;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 5.4rem auto;
|
||||
background-position: center center;
|
||||
font-style: italic;
|
||||
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
|
||||
transition-duration: 0.42s;
|
||||
}
|
||||
|
||||
smart-button.focused {
|
||||
outline: 0;
|
||||
background-color: #ffffff;
|
||||
transform: scale(1.2);
|
||||
transition-duration: 0.42s;
|
||||
}
|
||||
|
||||
smart-button.released {
|
||||
transition-duration: 0.16s;
|
||||
}
|
||||
|
||||
smart-button.toggled {
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button.pressed {
|
||||
background-color: #00caf2;
|
||||
transform: scale(0.8);
|
||||
transition-duration: 0.06s;
|
||||
}
|
||||
|
||||
smart-button.disabled {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
smart-button[data-icon]:before {
|
||||
width: 5.4rem;
|
||||
left: 1.3rem;
|
||||
font-size: 5.4rem;
|
||||
line-height: 8rem;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
white-space: normal;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
smart-button[data-icon].focused:before {
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[data-icon].toggled:before {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[data-icon].pressed:before {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
smart-button[data-icon].disabled:before {
|
||||
color: #ffffff;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Text Button
|
||||
**/
|
||||
smart-button[type="simple-text"] {
|
||||
color: #dddddd;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
width: auto;
|
||||
height: 8rem;
|
||||
|
||||
line-height: 8rem;
|
||||
font-size: 2.4rem;
|
||||
-moz-user-select: none;
|
||||
padding-left: 2.6rem;
|
||||
padding-right: 2.6rem;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].focused {
|
||||
color: #ffffff;
|
||||
background: none;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].toggled {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].pressed {
|
||||
color: #00caf2;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of Simple Text Button
|
||||
**/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* underlined text button
|
||||
*/
|
||||
smart-button[type="underlined-text"] {
|
||||
position: relative;
|
||||
color: white;
|
||||
font-size: 2.4rem;
|
||||
padding-left: 3.8rem;
|
||||
padding-right: 3.8rem;
|
||||
border-radius: 4rem;
|
||||
width: auto;
|
||||
line-height: 8rem;
|
||||
margin-right: 1.4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"]:after {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
border-bottom: 0.6rem #61acd7 solid;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of underlined text button
|
||||
*/
|
||||
|
||||
/**
|
||||
* App Icons
|
||||
**/
|
||||
smart-button[type="app-button"] {
|
||||
/* To use 33.4rem instead of to use 33.6rem is to work around the anti-alias
|
||||
pixel. So, the circle is filled by the background almost totally. */
|
||||
width: 33.4rem;
|
||||
height: 33.4rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].focused {
|
||||
transform: scale(1.4);
|
||||
/* To use outline will not change the size of button and make the whole button
|
||||
thrilling. */
|
||||
outline: 0.8rem solid #ffffff;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].pressed {
|
||||
/* While scaling, we may have a gap between outline and icon which may be
|
||||
anti-alias pixel. So, we use scale(1.22) to prevent such kind of case */
|
||||
transform: scale(1.22);
|
||||
/* To use outline will not change the size of button and make the whole button
|
||||
thrilling. */
|
||||
outline: 1.0rem solid #ffffff;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].fullsized {
|
||||
background-size: 33.6rem auto;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].fitted {
|
||||
background-size: 22rem auto;
|
||||
}
|
||||
|
||||
/** we use :after as label */
|
||||
smart-button[type="app-button"]:after {
|
||||
content: attr(label);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
right: auto;
|
||||
|
||||
width: 33.6rem;
|
||||
height: 8rem;
|
||||
|
||||
padding-top: 1rem;
|
||||
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
|
||||
font-size: 2.8rem;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* text backgorund color
|
||||
**/
|
||||
smart-button[type="app-button"][app-type="deck"]:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="tv"]:after {
|
||||
background-color: #d90c83;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="app"]:after,
|
||||
smart-button[type="app-button"][app-type="bookmark"]:after {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="device"]:after {
|
||||
background-color: #4a82ea;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="folder"] {
|
||||
background-color: #595959;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"]:before {
|
||||
left: 0;
|
||||
line-height: 30rem;
|
||||
font-size: 16rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="folder"]:before {
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of App Icons
|
||||
**/
|
||||
|
||||
/**
|
||||
* text and icon button
|
||||
*/
|
||||
smart-button[type="text-and-icon"] {
|
||||
position: relative;
|
||||
color: white;
|
||||
font-size: 2.4rem;
|
||||
padding-left: 4.6rem;
|
||||
padding-right: 4.6rem;
|
||||
border-radius: 4rem;
|
||||
width: auto;
|
||||
line-height: 8rem;
|
||||
margin-right: 1.4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"][data-icon] {
|
||||
padding-left: 9.6rem;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"][data-icon]:before {
|
||||
left: 3.8rem;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of text and icon button
|
||||
*/
|
||||
|
||||
/**
|
||||
* banner button
|
||||
**/
|
||||
smart-button[type="banner-button"] {
|
||||
position: relative;
|
||||
|
||||
color: #ffffff;
|
||||
font-size: 2rem;
|
||||
|
||||
width: 36rem;
|
||||
height: 20rem;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 0;
|
||||
border: 0 solid #ffffff;
|
||||
|
||||
background-color: #5f5f5f;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 120%;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1.0);
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"][label]:before {
|
||||
content: attr(label);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
height: 5rem;
|
||||
|
||||
line-height: 5rem;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"]:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
|
||||
transition-duration: 0.06s;
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].focused {
|
||||
transform: none;
|
||||
border-width: 1.2rem;
|
||||
background-size: 100%;
|
||||
transition-duration: 0.24s;
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].pressed {
|
||||
transform: scale(0.945, 0.9);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].pressed:after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* banner button
|
||||
**/
|
||||
|
||||
/**
|
||||
* circle text button
|
||||
*/
|
||||
smart-button[type="circle-text"] {
|
||||
width: 21rem;
|
||||
height: 21rem;
|
||||
color: white;
|
||||
background-color: #5f6368;
|
||||
line-height: 21rem;
|
||||
padding: 2.5rem 2rem 1.5rem 2rem;
|
||||
text-align: center;
|
||||
font-size: 3.2rem;
|
||||
font-weight: bolder;
|
||||
overflow: hidden;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
background-color: white;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary {
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary.focused {
|
||||
color: #ffffff;
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary.pressed {
|
||||
background-color: #0097b5;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger {
|
||||
background-color: #e63d2f;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger.focused {
|
||||
color: #ffffff;
|
||||
background-color: #e63d2f;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger.pressed {
|
||||
background-color: #ac2e23;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of circle text button
|
||||
*/
|
||||
|
||||
/**
|
||||
* rounded-corner text button
|
||||
*/
|
||||
smart-button[type="rounded-corner-text"] {
|
||||
-moz-user-select: none;
|
||||
width: auto;
|
||||
height: 8rem;
|
||||
border-radius: 4rem;
|
||||
background-color: #5f6368;
|
||||
padding: 0 3.8rem;
|
||||
line-height: 8rem;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 2.4rem;
|
||||
overflow: hidden;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].focused,
|
||||
smart-button[type="rounded-corner-text"]:hover {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].pressed {
|
||||
transform: scale(1);
|
||||
/*color: white;*/
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary {
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary:hover,
|
||||
smart-button[type="rounded-corner-text"].primary.focused {
|
||||
color: #ffffff;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary.pressed {
|
||||
background-color: #0097b5;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of rounded-corner text button
|
||||
*/
|
||||
25
gaia/tv_apps/app-deck/bower_components/smart-dialog/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "smart-dialog",
|
||||
"homepage": "https://github.com/smart-components/smart-dialog",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"_release": "8910d99b8f",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "8910d99b8fb7c624b32ac494fea63c0898e886df"
|
||||
},
|
||||
"_source": "git://github.com/smart-components/smart-dialog.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "smart-components/smart-dialog"
|
||||
}
|
||||
17
gaia/tv_apps/app-deck/bower_components/smart-dialog/bower.json
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "smart-dialog",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/smart-components/smart-dialog",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
123
gaia/tv_apps/app-deck/bower_components/smart-dialog/script.js
vendored
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* Smart-Dialog
|
||||
* - Event:
|
||||
* 1. will-open
|
||||
* 2. opened
|
||||
* 3. will-close
|
||||
* 4. closed
|
||||
* - API:
|
||||
* 1. open - open a smart-dialog: closed -> opening -> opened
|
||||
* 2. close - close a smart-dialog: opened -> closing -> closed
|
||||
* - Attribute:
|
||||
* 1. title - title of the smart-dialog
|
||||
* 2. esc-close - close dialog when ESC key is clicked (default true).
|
||||
* Default scale: 1920x1080 (2*sqrt(960*960 + 1080*1080) / 20 = 144.5)
|
||||
*/
|
||||
|
||||
window.SmartDialog = (function(win) {
|
||||
'use strict';
|
||||
|
||||
// The value of openDuration and closeDuration should be the same as
|
||||
// transitionDuration in opening and closing css class.
|
||||
const openDuration = 1000;
|
||||
const closeDuration = 650;
|
||||
|
||||
// Extend from the HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
proto.createdCallback = function sd_createdCallback() {
|
||||
this.addEventListener('transitionend', this);
|
||||
document.addEventListener('keyup', this);
|
||||
this.transitionTimer = null;
|
||||
this.classList.add('closed');
|
||||
if (this.hasAttribute('background-color')) {
|
||||
this.style.backgroundColor = this.getAttribute('background-color');
|
||||
}
|
||||
};
|
||||
|
||||
proto._onOpened = function sd_onOpened() {
|
||||
this.classList.remove('opening');
|
||||
this.classList.add('opened');
|
||||
this.fireEvent('opened');
|
||||
};
|
||||
|
||||
proto._onClosed = function sd_onClosed() {
|
||||
this.classList.remove('closing');
|
||||
this.classList.add('closed');
|
||||
this.fireEvent('closed');
|
||||
};
|
||||
|
||||
proto.open = function sd_open() {
|
||||
if (this.classList.contains('opened') ||
|
||||
this.classList.contains('opening')) {
|
||||
return;
|
||||
}
|
||||
this.fireEvent('will-open');
|
||||
this.classList.remove('closing');
|
||||
this.classList.remove('closed');
|
||||
this.classList.add('opening');
|
||||
// handle the case when transitionend event is not fired
|
||||
clearTimeout(this.transitionTimer);
|
||||
this.transitionTimer = setTimeout(function() {
|
||||
this._onOpened();
|
||||
this.transitionTimer = null;
|
||||
}.bind(this), openDuration);
|
||||
};
|
||||
|
||||
proto.close = function sd_close() {
|
||||
if (this.classList.contains('closed') ||
|
||||
this.classList.contains('closing')) {
|
||||
return;
|
||||
}
|
||||
this.fireEvent('will-close');
|
||||
this.classList.remove('opening');
|
||||
this.classList.remove('opened');
|
||||
this.classList.add('closing');
|
||||
// handle the case when transitionend event is not fired
|
||||
clearTimeout(this.transitionTimer);
|
||||
this.transitionTimer = setTimeout(function() {
|
||||
this._onClosed();
|
||||
this.transitionTimer = null;
|
||||
}.bind(this), closeDuration);
|
||||
};
|
||||
|
||||
proto.handleEvent = function sd_handleEvent(evt) {
|
||||
|
||||
switch(evt.type) {
|
||||
case 'transitionend':
|
||||
if (evt.target === this && this.transitionTimer) {
|
||||
if (this.classList.contains('opening')) {
|
||||
this._onOpened();
|
||||
} else if (this.classList.contains('closing')) {
|
||||
this._onClosed();
|
||||
}
|
||||
clearTimeout(this.transitionTimer);
|
||||
this.transitionTimer = null;
|
||||
}
|
||||
break;
|
||||
case 'keyup':
|
||||
// close dialog when Back is triggered
|
||||
if (this.getAttribute('esc-close') != 'false' &&
|
||||
(evt.keyCode === window.KeyEvent.DOM_VK_BACK_SPACE ||
|
||||
evt.key === 'Backspace') &&
|
||||
this.classList.contains('opened')) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this.close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
proto.fireEvent = function sd_fireEvent(event, detail) {
|
||||
var evtObject = new CustomEvent(event, {
|
||||
bubbles: false,
|
||||
detail: detail || this
|
||||
});
|
||||
this.dispatchEvent(evtObject);
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('smart-dialog', { prototype: proto });
|
||||
})(window);
|
||||
103
gaia/tv_apps/app-deck/bower_components/smart-dialog/style.css
vendored
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Smart-Dialog
|
||||
* - State:
|
||||
* 1. closed
|
||||
* 2. opening
|
||||
* 3. opened - content visible
|
||||
* 4. closing
|
||||
* Default scale: 1920x1080 (2*sqrt(960*960 + 1080*1080) / 20 = 144.5)
|
||||
*/
|
||||
|
||||
smart-dialog {
|
||||
position: fixed;
|
||||
z-index: 2147483647;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
smart-dialog:before {
|
||||
content: attr(title);
|
||||
display: block;
|
||||
margin-top: 5rem;
|
||||
margin-left: 7.6rem;
|
||||
font-family: "Fira Sans";
|
||||
font-size: 5.4rem;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
smart-dialog:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: fixed;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
top: calc(100% - 1rem);
|
||||
left: calc(50% - 1rem);
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
transition: none;
|
||||
transform: scale(20);
|
||||
}
|
||||
|
||||
smart-dialog.closed > *,
|
||||
smart-dialog.closing > *,
|
||||
smart-dialog.opening > *,
|
||||
smart-dialog.closed:before,
|
||||
smart-dialog.closing:before,
|
||||
smart-dialog.opening:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
smart-dialog.closed {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
smart-dialog.opened {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
smart-dialog.opened:after {
|
||||
visibility: hidden;
|
||||
border-radius: 50%;
|
||||
transform: scale(144.5);
|
||||
}
|
||||
|
||||
smart-dialog.opening:after {
|
||||
border-radius: 50%;
|
||||
transform: scale(144.5);
|
||||
/* transitionDuration should be the same as openDuration variable in script */
|
||||
transition: transform 1s cubic-bezier(0, 0, 0 ,1);
|
||||
}
|
||||
|
||||
smart-dialog.closing:after {
|
||||
border-radius: 50%;
|
||||
transform: scale(20);
|
||||
/* transitionDuration should be the same as closeDuration variable in script */
|
||||
transition: transform 0.65s cubic-bezier(0.6, 0, 1 ,1);
|
||||
}
|
||||
|
||||
/* 1280x800 for flatfish */
|
||||
@media (width: 1280px) and (height: 800px) {
|
||||
smart-dialog:after,
|
||||
smart-dialog.closing:after {
|
||||
transform: scale(7.5);
|
||||
}
|
||||
|
||||
smart-dialog.opening:after {
|
||||
transform: scale(102.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 800x1280 for flatfish vertical */
|
||||
@media (width: 800px) and (height: 1280px) {
|
||||
smart-dialog:after,
|
||||
smart-dialog.closing:after {
|
||||
transform: scale(8.3);
|
||||
}
|
||||
|
||||
smart-dialog.opening:after {
|
||||
transform: scale(134);
|
||||
}
|
||||
}
|
||||
31
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "smart-modal-dialog",
|
||||
"homepage": "https://github.com/smart-components/smart-modal-dialog",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"smart-dialog": "smart-components/smart-dialog",
|
||||
"smart-bubbles": "smart-components/smart-bubbles",
|
||||
"smart-button": "smart-components/smart-button",
|
||||
"smart-icons": "smart-components/smart-icons"
|
||||
},
|
||||
"_release": "3b62d9317c",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "3b62d9317cdd42e3e9604a5e6a59ce1edbff81cf"
|
||||
},
|
||||
"_source": "git://github.com/smart-components/smart-modal-dialog.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "smart-components/smart-modal-dialog"
|
||||
}
|
||||
362
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/LICENSE
vendored
Executable file
|
|
@ -0,0 +1,362 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
122
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/base-modal-dialog.js
vendored
Executable file
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* BaseModalDialog creates a dialog and handles esc key to close it. It also
|
||||
* includes open/close animation.
|
||||
* The template is shown below:
|
||||
* <smart-dialog class="modal-dialog">
|
||||
* <div class="outer-container">
|
||||
* <div class="container"></div>
|
||||
* </div>
|
||||
* </smart-dialog>
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
function BaseModalDialog(container) {
|
||||
// Determine whether this dialog is opened or not
|
||||
this.isOpened = false;
|
||||
|
||||
// smart-dialog helps us to handle ESC key to close the dialog.
|
||||
this.element = document.createElement('smart-dialog');
|
||||
this.element.classList.add('modal-dialog');
|
||||
this.element.setAttribute('role', 'dialog');
|
||||
// make dialog focusable in order to catch focus from mouse click or touch
|
||||
// event.
|
||||
this.element.setAttribute('tabIndex', '-1');
|
||||
|
||||
// in order to make vertical align, we need additional container
|
||||
this.outerContainer = document.createElement('div');
|
||||
this.outerContainer.classList.add('outer-container');
|
||||
|
||||
this.innerContainer = document.createElement('div');
|
||||
this.innerContainer.classList.add('container');
|
||||
|
||||
this.outerContainer.appendChild(this.innerContainer);
|
||||
this.element.appendChild(this.outerContainer);
|
||||
|
||||
this.container = container || document.body;
|
||||
this.container.appendChild(this.element);
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
var proto = Object.create(SmartDialog.prototype);
|
||||
|
||||
proto._render = function() {
|
||||
// implement rendering here.
|
||||
};
|
||||
|
||||
proto._init = function(container, options) {
|
||||
// Put your custom init code here.
|
||||
};
|
||||
|
||||
proto._setL10n = function(element, l10n) {
|
||||
if ((typeof l10n) === 'string') {
|
||||
element.setAttribute('data-l10n-id', l10n);
|
||||
} else if (navigator.mozL10n) {
|
||||
navigator.mozL10n.setAttributes(element, l10n.id, l10n.args);
|
||||
}
|
||||
};
|
||||
|
||||
proto._open = function(options) {
|
||||
// If you want to override this, please call this at function at the end of
|
||||
// yours.
|
||||
|
||||
// onCancel is triggered when ESC key is pressed.
|
||||
this.onCancel = options.onCancel || function() {};
|
||||
|
||||
this.element.classList.add('visible');
|
||||
this.element.open();
|
||||
this.element.focus();
|
||||
};
|
||||
|
||||
proto.open = function(options) {
|
||||
if (this.isOpened) {
|
||||
return;
|
||||
}
|
||||
this.isOpened = true;
|
||||
// We should wait two frames for reflow.
|
||||
window.requestAnimationFrame(function() {
|
||||
window.requestAnimationFrame(this._open.bind(this, options));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
proto.close = function() {
|
||||
this.element.close();
|
||||
this.element.focus();
|
||||
};
|
||||
|
||||
proto.remove = function() {
|
||||
this.container.removeChild(this.element);
|
||||
};
|
||||
|
||||
proto._focusContent = function() {};
|
||||
|
||||
proto._blurContent = function() {};
|
||||
|
||||
proto.focus = function() {
|
||||
if (this.element.classList.contains('opening') ||
|
||||
this.element.classList.contains('closing')) {
|
||||
this.element.focus();
|
||||
} else {
|
||||
this._focusContent();
|
||||
}
|
||||
};
|
||||
|
||||
proto.blur = function() {
|
||||
this._blurContent();
|
||||
};
|
||||
|
||||
proto.fireEvent = function smd_fireEvent(event, detail) {
|
||||
var evtObject = new CustomEvent(event, {
|
||||
bubbles: true,
|
||||
detail: detail || this
|
||||
});
|
||||
this.container.dispatchEvent(evtObject);
|
||||
};
|
||||
|
||||
|
||||
BaseModalDialog.prototype = proto;
|
||||
exports.BaseModalDialog = BaseModalDialog;
|
||||
|
||||
})(window);
|
||||
23
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/bower.json
vendored
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "smart-modal-dialog",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/smart-components/smart-modal-dialog",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"smart-dialog": "smart-components/smart-dialog",
|
||||
"smart-bubbles": "smart-components/smart-bubbles",
|
||||
"smart-button": "smart-components/smart-button",
|
||||
"smart-icons": "smart-components/smart-icons"
|
||||
}
|
||||
}
|
||||
60
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/multiple-value-selector.css
vendored
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
.multiple-value-selector .selector-list-container {
|
||||
display: inline-block;
|
||||
margin-left: 23.8rem;
|
||||
margin-right: 15rem;
|
||||
max-height: 70.8rem;
|
||||
overflow-y: hidden;
|
||||
/* 67.2rem - 23.8rem - 15rem */
|
||||
width: calc(100% - 106rem);
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
scroll-behavior: smooth;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.multiple-value-selector .modal-dialog-button-group {
|
||||
margin-right: 20.8rem;
|
||||
width: 46.4rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.multiple-value-selector .selector-list-item {
|
||||
display: block;
|
||||
/* substract 9.4rem for tick icon */
|
||||
width: calc(100% - 9.4rem);
|
||||
height: 13.6rem;
|
||||
font-family: "Fira Sans";
|
||||
font-size: 4.4rem;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 13.6rem;
|
||||
transition: color 0.2s ease-in-out, transform 0.2s ease-in-out;
|
||||
position: relative;
|
||||
/* refer https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.multiple-value-selector .selector-list-item.checked:after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
font-family: 'smart-icons';
|
||||
content: 'check';
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.multiple-value-selector .selector-list-item.focused {
|
||||
color: #00caf2;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.multiple-value-selector .selector-list-item.focused.checked:after {
|
||||
color: #00caf2;
|
||||
transform: scale(1);
|
||||
}
|
||||
343
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/multiple-value-selector.js
vendored
Executable file
|
|
@ -0,0 +1,343 @@
|
|||
/**
|
||||
* MultipleValueSelector creates a list of options, and a dialog with ok/cancel
|
||||
* buttons.
|
||||
* The template is shown below:
|
||||
* <smart-dialog class="modal-dialog multiple-value-selector">
|
||||
* <div class="outer-container">
|
||||
* <div class="container">
|
||||
* <ul class="selector-list-container">
|
||||
* <li class="selector-list-item checked">Item 1</li>
|
||||
* <li class="selector-list-item focused">Item 2</li>
|
||||
* <li class="selector-list-item">Item 3</li>
|
||||
* </ul>
|
||||
* <div class="modal-dialog-button-group">
|
||||
* <smart-button></smart-button>
|
||||
* ...
|
||||
* <smart-button></smart-button>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </smart-dialog>
|
||||
*
|
||||
* options for open method:
|
||||
* {
|
||||
* list: [... array of items ...]
|
||||
* buttonSettings: { // if we don't have button settings, we use Ok and
|
||||
* l10n: { // Cancel as text content.
|
||||
* ok: 'l10n-ok',
|
||||
* cancel: 'l10n-cancel'
|
||||
* }
|
||||
* },
|
||||
* onSelected: function() {} // called while user press ok
|
||||
* onCancel: function() {} // called while user press esc or cancel
|
||||
* }
|
||||
*
|
||||
* list item:
|
||||
* {
|
||||
* checked: false, // if checked by default, default value is false.
|
||||
* textRaw: 'raw text',
|
||||
* textL10nId: 'text or object for l10n',
|
||||
* defaultFocus: true // the default focus item
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var NORMAL_LIST_ITEM_SIZE = 136;
|
||||
var SELECTED_LIST_ITEM_SIZE = 164;
|
||||
|
||||
function MultipleValueSelector(container) {
|
||||
// call constructor of smart modal dialog.
|
||||
BaseModalDialog.call(this, container);
|
||||
}
|
||||
// extend from smart modal dialog
|
||||
var proto = Object.create(BaseModalDialog.prototype);
|
||||
|
||||
var NAVIGATION_INDEX = Object.freeze({
|
||||
'LIST': 0,
|
||||
'CANCEL': 1,
|
||||
'OK': 2
|
||||
});
|
||||
|
||||
proto._init = function() {
|
||||
this._selectedItems = [];
|
||||
this._clickedButton = null;
|
||||
// listContainer has scroll bar, value selector will calculate the
|
||||
// scroll top while focus it.
|
||||
this.listContainer = document.createElement('ul');
|
||||
this.listContainer.setAttribute('role', 'list');
|
||||
this.listContainer.tabIndex = 0;
|
||||
this.listContainer.classList.add('selector-list-container');
|
||||
this.innerContainer.appendChild(this.listContainer);
|
||||
|
||||
// render button group
|
||||
this.buttonGroup = document.createElement('div');
|
||||
this.buttonGroup.classList.add('modal-dialog-button-group');
|
||||
this.buttonGroup.setAttribute('smart-bubbles', 'true');
|
||||
|
||||
this.innerContainer.appendChild(this.buttonGroup);
|
||||
|
||||
this.smartBubble = document.createElement('smart-bubbles');
|
||||
this.element.classList.add('multiple-value-selector');
|
||||
|
||||
this._initEventListeners();
|
||||
|
||||
this._listFocus = 0;
|
||||
this._moduleFocus = NAVIGATION_INDEX.LIST;
|
||||
};
|
||||
|
||||
proto._initEventListeners = function() {
|
||||
this.element.addEventListener('will-open', this);
|
||||
this.element.addEventListener('will-close', this);
|
||||
this.element.addEventListener('opened', this);
|
||||
this.element.addEventListener('closed', this);
|
||||
this.smartBubble.addEventListener('all-items-bubbled', this);
|
||||
};
|
||||
|
||||
proto._render = function() {
|
||||
this._renderList();
|
||||
this._renderButtons();
|
||||
};
|
||||
|
||||
proto._renderList = function() {
|
||||
this.listContainer.innerHTML = '';
|
||||
this._options.list.forEach(function(item, index) {
|
||||
var li = document.createElement('li');
|
||||
li.setAttribute('role', 'listitem');
|
||||
li.classList.add('selector-list-item');
|
||||
if (item.checked) {
|
||||
li.classList.add('checked');
|
||||
}
|
||||
if (item.textRaw) {
|
||||
li.textContent = item.textRaw;
|
||||
} else if (item.textL10nId) {
|
||||
this._setL10n(li, item.textL10nId);
|
||||
}
|
||||
if (item.defaultFocus) {
|
||||
this._defaultListFocus = index;
|
||||
}
|
||||
this.listContainer.appendChild(li);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
proto._renderButtons = function() {
|
||||
this.buttonGroup.innerHTML = '';
|
||||
var buttonSettings = this._options.buttonSettings;
|
||||
|
||||
var okButton = document.createElement('smart-button');
|
||||
okButton.setAttribute('type', 'circle-text');
|
||||
if (buttonSettings && buttonSettings.l10n && buttonSettings.l10n.ok) {
|
||||
this._setL10n(okButton, buttonSettings.l10n.ok);
|
||||
} else {
|
||||
okButton.textContent = 'Ok';
|
||||
}
|
||||
okButton.classList.add('primary');
|
||||
okButton.addEventListener('click', this);
|
||||
|
||||
var cancelButton = document.createElement('smart-button');
|
||||
cancelButton.setAttribute('type', 'circle-text');
|
||||
if (buttonSettings && buttonSettings.l10n && buttonSettings.l10n.cancel) {
|
||||
this._setL10n(cancelButton, buttonSettings.l10n.cancel);
|
||||
} else {
|
||||
cancelButton.textContent = 'cancel';
|
||||
}
|
||||
cancelButton.addEventListener('click', this);
|
||||
|
||||
this.buttonGroup.appendChild(cancelButton);
|
||||
this.buttonGroup.appendChild(okButton);
|
||||
this.okButton = okButton;
|
||||
this.cancelButton = cancelButton;
|
||||
};
|
||||
|
||||
proto.handleEvent = function (e) {
|
||||
|
||||
if (e.type === 'keydown') {
|
||||
this._handleKeyDownEvent(e);
|
||||
} else if (e.type === 'keyup') {
|
||||
this._handleKeyUpEvent(e);
|
||||
} else if (e.type === 'click') {
|
||||
this._clickedButton = e.target;
|
||||
this.element.close();
|
||||
this.element.focus();
|
||||
} else {
|
||||
switch(e.target) {
|
||||
case this.element:
|
||||
if (e.type === 'will-open' || e.type === 'will-close') {
|
||||
this.fireEvent('modal-dialog-' + e.type);
|
||||
} else if (e.type === 'opened') {
|
||||
this.scrollToIndex(this._listFocus);
|
||||
// Play bubble animation when the smart-dialog is opened
|
||||
this.smartBubble.play([this.cancelButton, this.okButton]);
|
||||
} else if (e.type === 'closed') {
|
||||
// Do click actions when the smart-dialog is closed
|
||||
this.element.classList.remove('visible');
|
||||
if (this._clickedButton === this.okButton && this._onSelected) {
|
||||
this._onSelected(this._selectedItems);
|
||||
} else if (this.onCancel) {
|
||||
// ESC key enter
|
||||
this.onCancel();
|
||||
}
|
||||
this.isOpened = false;
|
||||
this.stopKeyNavigation();
|
||||
this.element.classList.remove('modal-dialog-opened');
|
||||
this.fireEvent('modal-dialog-closed');
|
||||
}
|
||||
break;
|
||||
case this.smartBubble:
|
||||
// all items bubbled
|
||||
this.element.classList.add('modal-dialog-opened');
|
||||
this.startKeyNavigation();
|
||||
this.focus();
|
||||
this.fireEvent('modal-dialog-opened');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proto.startKeyNavigation = function() {
|
||||
this.element.addEventListener('keydown', this);
|
||||
this.element.addEventListener('keyup', this);
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.stopKeyNavigation = function() {
|
||||
this.element.removeEventListener('keyup', this);
|
||||
};
|
||||
|
||||
proto._handleKeyDownEvent = function(e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_UP) {
|
||||
this.moveUp(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_DOWN) {
|
||||
this.moveDown(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
|
||||
this.moveLeft(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
|
||||
this.moveRight(e);
|
||||
} else if (e.keyCode == KeyEvent.DOM_VK_TAB) {
|
||||
e.preventDefault();
|
||||
if (this._moduleFocus === 2) {
|
||||
this._moduleFocus = 0;
|
||||
} else {
|
||||
this._moduleFocus = this._moduleFocus === 2 ? 0 : this._moduleFocus + 1;
|
||||
}
|
||||
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
proto._handleKeyUpEvent = function(e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_RETURN && this._focusedListItem) {
|
||||
this._focusedListItem.classList.toggle('checked');
|
||||
if (this._focusedListItem.classList.contains('checked')) {
|
||||
this._selectedItems.push(this._itemList[this._listFocus]);
|
||||
} else {
|
||||
var idx = this._selectedItems.indexOf(this._itemList[this._listFocus]);
|
||||
if (idx > -1) {
|
||||
this._selectedItems.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle list focus
|
||||
proto.moveUp = function(e) {
|
||||
if (this._moduleFocus !== NAVIGATION_INDEX.LIST ||
|
||||
this._listFocus < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._listFocus--;
|
||||
this.focus();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
proto.moveDown = function(e) {
|
||||
if (this._moduleFocus !== NAVIGATION_INDEX.LIST ||
|
||||
this._listFocus > this._itemList.length - 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._listFocus++;
|
||||
this.focus();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// handle module focus
|
||||
proto.moveLeft = function(e) {
|
||||
if (this._moduleFocus < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._moduleFocus--;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.moveRight = function(e) {
|
||||
// we only have three modules: list, cancel, and ok
|
||||
if (this._moduleFocus > 1) {
|
||||
// Do nothing when focus index is at the last element
|
||||
return;
|
||||
}
|
||||
|
||||
this._moduleFocus++;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.scrollToIndex = function(index) {
|
||||
var top = this.listContainer.scrollTop;
|
||||
var bottom = top + this.listContainer.clientHeight;
|
||||
|
||||
var itemTop = NORMAL_LIST_ITEM_SIZE * index;
|
||||
var itemBottom = itemTop + SELECTED_LIST_ITEM_SIZE;
|
||||
|
||||
if (top > itemTop) {
|
||||
// item is at top
|
||||
this.listContainer.scrollTop = itemTop;
|
||||
} else if (bottom < itemBottom) {
|
||||
// item is at bottom
|
||||
this.listContainer.scrollTop += (itemBottom - bottom);
|
||||
}
|
||||
};
|
||||
|
||||
proto._focusContent = function() {
|
||||
if (this._focusedListItem) {
|
||||
this._focusedListItem.classList.remove('focused');
|
||||
this._focusedListItem = null;
|
||||
this.listContainer.classList.remove('focused');
|
||||
}
|
||||
|
||||
switch(this._moduleFocus) {
|
||||
case NAVIGATION_INDEX.LIST:
|
||||
this._focusedListItem = this.listContainer.children[this._listFocus];
|
||||
this._focusedListItem.classList.add('focused');
|
||||
this.listContainer.classList.add('focused');
|
||||
this.scrollToIndex(this._listFocus);
|
||||
this.listContainer.focus();
|
||||
break;
|
||||
case NAVIGATION_INDEX.CANCEL:
|
||||
this.cancelButton.focus();
|
||||
break;
|
||||
case NAVIGATION_INDEX.OK:
|
||||
this.okButton.focus();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
proto._open = function(options) {
|
||||
this._options = options;
|
||||
this._itemList = options.list;
|
||||
this._render();
|
||||
this._listFocus = this._defaultListFocus || 0;
|
||||
this._moduleFocus = NAVIGATION_INDEX.LIST;
|
||||
|
||||
this._onSelected = this._options.onSelected;
|
||||
|
||||
BaseModalDialog.prototype._open.call(this, options);
|
||||
};
|
||||
|
||||
MultipleValueSelector.prototype = proto;
|
||||
exports.MultipleValueSelector = MultipleValueSelector;
|
||||
|
||||
})(window);
|
||||
276
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/smart-input-dialog.js
vendored
Executable file
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* SmartInputDialog creates an input text box with a clear button to clear text,
|
||||
* and a dialog with a group of buttons as select options, Text box includes
|
||||
* focus/blur animation, buttons includes open/close and bubbling animation.
|
||||
* The template is shown below:
|
||||
* <smart-dialog class="modal-dialog">
|
||||
* <div class="outer-container">
|
||||
* <div class="container">
|
||||
* <div class="modal-dialog-message-container">
|
||||
* <div class="modal-dialog-message"></div>
|
||||
* <div class="modal-dialog-custom-group"></div>
|
||||
* <div class="modal-dialog-input-group">
|
||||
* <input></input>
|
||||
* <button class="clear"></button>
|
||||
* <div class="focus"></div>
|
||||
* </div>
|
||||
* </div>
|
||||
* <div class="modal-dialog-button-group">
|
||||
* <smart-button></smart-button>
|
||||
* ...
|
||||
* <smart-button></smart-button>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </smart-dialog>
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_MARGIN = 44;
|
||||
var INPUT_GROUP_INDEX = 0;
|
||||
var BUTTON_GROUP_INDEX = 1;
|
||||
|
||||
function SmartInputDialog(container, options) {
|
||||
SmartModalDialog.call(this, container, options);
|
||||
}
|
||||
|
||||
var proto = Object.create(SmartModalDialog.prototype);
|
||||
|
||||
proto._render = function() {
|
||||
SmartModalDialog.prototype._render.call(this);
|
||||
this.textInput = document.createElement('input');
|
||||
this.textInput.addEventListener('keydown', function(e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
|
||||
if (this.selectionEnd < this.value.length) {
|
||||
// We are going to move cursor in the text input, calling
|
||||
// stopPropagation to prevent focus being transfer to clear button.
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.clearButton = document.createElement('button');
|
||||
this.clearButton.setAttribute('data-icon', 'close');
|
||||
this.clearButton.classList.add('clear');
|
||||
this.clearButton.addEventListener('click', function() {
|
||||
this.textInput.value = '';
|
||||
this.textInput.focus();
|
||||
}.bind(this));
|
||||
|
||||
this.focusBar = document.createElement('div');
|
||||
this.focusBar.classList.add('focus');
|
||||
|
||||
this.smartInputGroup = document.createElement('div');
|
||||
this.smartInputGroup.classList.add('modal-dialog-input-group');
|
||||
this.smartInputGroup.appendChild(this.textInput);
|
||||
this.smartInputGroup.appendChild(this.clearButton);
|
||||
this.smartInputGroup.appendChild(this.focusBar);
|
||||
|
||||
this.messageContainer.appendChild(this.smartInputGroup);
|
||||
};
|
||||
|
||||
proto._init = function() {
|
||||
this.defaultFocusIndex = [-1, -1];
|
||||
SmartModalDialog.prototype._init.call(this);
|
||||
};
|
||||
|
||||
proto._createMessageGroup = function(options) {
|
||||
this.message = options.message || {};
|
||||
|
||||
if (!this.message.textRaw && !this.message.textL10nId) {
|
||||
this.messageElement.classList.add('hidden');
|
||||
} else {
|
||||
this.messageElement.classList.remove('hidden');
|
||||
this.messageElement.textContent = this.message.textRaw || '';
|
||||
if (this.message.textL10nId) {
|
||||
this.messageElement.setAttribute('data-l10n-id',
|
||||
this.message.textL10nId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proto._createCustomGroup = function(options) {
|
||||
this.customElements = [];
|
||||
this.customElementOffset = 0;
|
||||
|
||||
this.customSettings = options.customElementSettings || null;
|
||||
|
||||
if (this.customSettings) {
|
||||
while (this.customElementGroup.firstChild) {
|
||||
this.customElementGroup.removeChild(this.customElementGroup.firstChild);
|
||||
}
|
||||
this.customElementGroup.appendChild(this.customSettings.element);
|
||||
this.customElementGroup.classList.remove('hidden');
|
||||
} else {
|
||||
this.customElementGroup.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
proto._createInputGroup = function(options) {
|
||||
this.inputElements = [this.textInput, this.clearButton];
|
||||
|
||||
if (options.initialInputValue) {
|
||||
this.textInput.value = options.initialInputValue;
|
||||
}
|
||||
|
||||
var retrunedCallback = options.onReturned;
|
||||
if (retrunedCallback) {
|
||||
this.textInput.addEventListener('keydown', function(evt) {
|
||||
if (evt.keyCode === KeyEvent.DOM_VK_RETURN &&
|
||||
this._getFocusedElement() !== this.clearButton) {
|
||||
retrunedCallback();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
proto._createButtonGroup = function(options) {
|
||||
this.buttonElements = [];
|
||||
|
||||
// onCancel is triggered when ESC key is pressed.
|
||||
this.onCancel = options.onCancel || function() {};
|
||||
this.buttonElements = [];
|
||||
this.buttonSettings = options.buttonSettings || [];
|
||||
|
||||
this.buttonGroup.innerHTML = '';
|
||||
this._focusedIndex[BUTTON_GROUP_INDEX] = 0;
|
||||
|
||||
// Set up every button
|
||||
this.buttonSettings.forEach(function buildButton(buttonSetting, index) {
|
||||
var button = document.createElement('smart-button');
|
||||
button.setAttribute('type', buttonSetting.type || 'circle-text');
|
||||
if (buttonSetting.textL10nId) {
|
||||
button.setAttribute('data-l10n-id', buttonSetting.textL10nId);
|
||||
}
|
||||
button.textContent = buttonSetting.textRaw || 'OK';
|
||||
button.classList.add(buttonSetting.class || 'confirm');
|
||||
if (buttonSetting.icon) {
|
||||
button.style.backgroundImage = 'url(' + buttonSetting.icon + ')';
|
||||
} else if (buttonSetting.iconFont) {
|
||||
button.dataset.icon = buttonSetting.iconFont;
|
||||
}
|
||||
button.addEventListener('click', function() {
|
||||
// Click action will be handled in closed event
|
||||
this._clickedIndex = index;
|
||||
this.element.close();
|
||||
this.element.focus();
|
||||
}.bind(this));
|
||||
this.buttonElements.push(button);
|
||||
this.buttonGroup.appendChild(button);
|
||||
|
||||
var customFocus = this.customSettings && this.customSettings.defaultFocus;
|
||||
if (buttonSetting.defaultFocus && !customFocus) {
|
||||
this._focusedIndex[BUTTON_GROUP_INDEX] = index;
|
||||
}
|
||||
|
||||
var renderedCallback = options.onButtonRendered;
|
||||
if (renderedCallback) {
|
||||
// We add render callback to let user to do more complex visual
|
||||
// modifications.
|
||||
renderedCallback(button, buttonSetting);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
proto._open = function(options) {
|
||||
this._focusedIndex = this.defaultFocusIndex;
|
||||
this._createMessageGroup(options);
|
||||
this._createCustomGroup(options);
|
||||
this._createInputGroup(options);
|
||||
this._createButtonGroup(options);
|
||||
|
||||
this._focusedGroupIndex = 0;
|
||||
this._focusedGroupIndex[INPUT_GROUP_INDEX] = 0;
|
||||
// Put focusable element groups into verticalGroup,
|
||||
// so we can simply navigate up/down among these groups.
|
||||
this.verticalGroup = [this.inputElements, this.buttonElements];
|
||||
|
||||
this.element.classList.add('visible');
|
||||
this.element.open();
|
||||
this.element.focus();
|
||||
};
|
||||
|
||||
proto.startKeyNavigation = function() {
|
||||
this.element.addEventListener('keydown', this);
|
||||
if (this._focusedIndex[0] === -1) {
|
||||
this._focusedIndex[0] = 0;
|
||||
}
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto._getFocusedElement = function() {
|
||||
return this.verticalGroup[this._focusedGroupIndex]
|
||||
[this._focusedIndex[this._focusedGroupIndex]];
|
||||
};
|
||||
|
||||
proto.moveUp = function(e) {
|
||||
if (this._focusedGroupIndex < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusedGroupIndex--;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.moveDown = function(e) {
|
||||
if (this._focusedGroupIndex > this.verticalGroup.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move input-group's focus back to input box when focus on button-group
|
||||
this._focusedIndex[INPUT_GROUP_INDEX] = 0;
|
||||
this._focusedGroupIndex++;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.movePrevious = function(e) {
|
||||
if (this._focusedIndex[this._focusedGroupIndex] < 1) {
|
||||
// Do nothing when focus index is at the first element.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusedIndex[this._focusedGroupIndex]--;
|
||||
if (this._focusedGroupIndex === INPUT_GROUP_INDEX) {
|
||||
// Moving from clean button to input text will move input caret position
|
||||
// back one character, preventDefault so we don't change the position.
|
||||
e.preventDefault();
|
||||
}
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.moveNext = function(e) {
|
||||
if (this._focusedIndex[this._focusedGroupIndex] >
|
||||
this.verticalGroup[this._focusedGroupIndex].length - 2) {
|
||||
// Do nothing when focus index is at the last element
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusedIndex[this._focusedGroupIndex]++;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto._handleKeyEvent = function(e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_UP) {
|
||||
this.moveUp(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_DOWN) {
|
||||
this.moveDown(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
|
||||
this.movePrevious(e);
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
|
||||
this.moveNext(e);
|
||||
} else if (e.keyCode == KeyEvent.DOM_VK_TAB) {
|
||||
e.preventDefault();
|
||||
if (this._focusedGroupIndex === INPUT_GROUP_INDEX) {
|
||||
this.moveDown(e);
|
||||
} else {
|
||||
this.moveUp(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SmartInputDialog.prototype = proto;
|
||||
exports.SmartInputDialog = SmartInputDialog;
|
||||
|
||||
})(window);
|
||||
293
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/smart-modal-dialog.js
vendored
Executable file
|
|
@ -0,0 +1,293 @@
|
|||
/**
|
||||
* SmartModalDialog creates a dialog with a group of buttons as select
|
||||
* options. It also includes open/close animation and bubbling animation.
|
||||
* The template is shown below:
|
||||
* <smart-dialog class="modal-dialog">
|
||||
* <div class="outer-container">
|
||||
* <div class="container">
|
||||
* <div class="modal-dialog-message-container">
|
||||
* <div class="modal-dialog-message"></div>
|
||||
* <div class="modal-dialog-custom-group"></div>
|
||||
* </div>
|
||||
* <div class="modal-dialog-button-group">
|
||||
* <smart-button></smart-button>
|
||||
* ...
|
||||
* <smart-button></smart-button>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </smart-dialog>
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_MARGIN = 44;
|
||||
|
||||
function SmartModalDialog(container, options) {
|
||||
// Determine whether this dialog is opened or not
|
||||
this._clickedIndex = null;
|
||||
this.defaultFocusIndex = -1;
|
||||
|
||||
this._margin = options && options.margin ? options.margin : DEFAULT_MARGIN;
|
||||
this._translateX = 0;
|
||||
|
||||
BaseModalDialog.call(this, container);
|
||||
}
|
||||
|
||||
var proto = Object.create(BaseModalDialog.prototype);
|
||||
|
||||
proto._init = function() {
|
||||
this._render();
|
||||
|
||||
this.smartBubble = document.createElement('smart-bubbles');
|
||||
this.element.addEventListener('will-open', this);
|
||||
this.element.addEventListener('will-close', this);
|
||||
this.element.addEventListener('opened', this);
|
||||
this.element.addEventListener('closed', this);
|
||||
this.smartBubble.addEventListener('all-items-bubbled', this);
|
||||
};
|
||||
|
||||
proto._render = function() {
|
||||
this.messageElement = document.createElement('div');
|
||||
this.messageElement.classList.add('modal-dialog-message');
|
||||
|
||||
this.customElementGroup = document.createElement('div');
|
||||
this.customElementGroup.classList.add('modal-dialog-custom-group');
|
||||
|
||||
this.messageContainer = document.createElement('div');
|
||||
this.messageContainer.classList.add('modal-dialog-message-container');
|
||||
this.messageContainer.appendChild(this.messageElement);
|
||||
this.messageContainer.appendChild(this.customElementGroup);
|
||||
|
||||
this.buttonGroup = document.createElement('div');
|
||||
this.buttonGroup.classList.add('modal-dialog-button-group');
|
||||
this.buttonGroup.setAttribute('smart-bubbles', 'true');
|
||||
|
||||
this.innerContainer.appendChild(this.messageContainer);
|
||||
this.innerContainer.appendChild(this.buttonGroup);
|
||||
};
|
||||
|
||||
proto._renderMessage = function(options) {
|
||||
this.message = options.message || {};
|
||||
|
||||
this.customSettings = options.customElementSettings || null;
|
||||
if (this.customSettings) {
|
||||
while (this.customElementGroup.firstChild) {
|
||||
this.customElementGroup.removeChild(this.customElementGroup.firstChild);
|
||||
}
|
||||
this.customElementGroup.appendChild(this.customSettings.element);
|
||||
this.customElementGroup.classList.remove('hidden');
|
||||
} else {
|
||||
this.customElementGroup.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (!this.message.textRaw && !this.message.textL10nId) {
|
||||
this.messageElement.classList.add('hidden');
|
||||
} else {
|
||||
this.messageElement.classList.remove('hidden');
|
||||
this.messageElement.textContent = this.message.textRaw || '';
|
||||
if (this.message.textL10nId) {
|
||||
this._setL10n(this.messageElement, this.message.textL10nId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proto._renderButtons = function(options) {
|
||||
var renderedCallback = options.onButtonRendered;
|
||||
this.buttonElements = [];
|
||||
this.buttonSettings = options.buttonSettings || [];
|
||||
this.buttonGroup.innerHTML = '';
|
||||
|
||||
// Set up every button
|
||||
this.buttonSettings.forEach(function buildButton(buttonSetting, index) {
|
||||
var button = document.createElement('smart-button');
|
||||
button.setAttribute('type', buttonSetting.type || 'circle-text');
|
||||
button.textContent = buttonSetting.textRaw || 'OK';
|
||||
if (buttonSetting.textL10nId) {
|
||||
this._setL10n(button, buttonSetting.textL10nId);
|
||||
}
|
||||
button.classList.add(buttonSetting.class || 'confirm');
|
||||
if (buttonSetting.icon) {
|
||||
button.style.backgroundImage = 'url(' + buttonSetting.icon + ')';
|
||||
} else if (buttonSetting.iconFont) {
|
||||
button.dataset.icon = buttonSetting.iconFont;
|
||||
}
|
||||
button.addEventListener('click', function() {
|
||||
// Click action will be handled in closed event
|
||||
this._clickedIndex = index;
|
||||
this.element.close();
|
||||
this.element.focus();
|
||||
}.bind(this));
|
||||
this.buttonElements.push(button);
|
||||
this.buttonGroup.appendChild(button);
|
||||
this._focusedIndex =
|
||||
(buttonSetting.defaultFocus) ? index : this._focusedIndex;
|
||||
if (renderedCallback) {
|
||||
// We add render callback to let user to do more complex visual
|
||||
// modifications.
|
||||
renderedCallback(button, buttonSetting);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
proto._open = function(options) {
|
||||
this._focusedIndex = this.defaultFocusIndex;
|
||||
this._renderMessage(options);
|
||||
this._renderButtons(options);
|
||||
BaseModalDialog.prototype._open.call(this, options);
|
||||
};
|
||||
|
||||
proto.startKeyNavigation = function() {
|
||||
this.element.addEventListener('keydown', this);
|
||||
if (this.buttonElements.length > 0) {
|
||||
if (this._focusedIndex === -1) {
|
||||
this._focusedIndex = 0;
|
||||
}
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the list to the input element
|
||||
*/
|
||||
proto._scrollTo = function(element) {
|
||||
var newTransition = this._getScrollOffset(element);
|
||||
this._translateX = newTransition;
|
||||
this.buttonGroup.style.transform = 'translateX(' + this._translateX +
|
||||
'px)';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the offset of the node element
|
||||
*/
|
||||
proto._getScrollOffset = function(nodeElem) {
|
||||
if (!this._viewWidth) {
|
||||
this._viewWidth = this.innerContainer.clientWidth - 2 * this._margin;
|
||||
}
|
||||
var nodeLeft = nodeElem.offsetLeft;
|
||||
var nodeWidth = nodeElem.offsetWidth;
|
||||
var listWidth = this.buttonGroup.offsetWidth;
|
||||
var newTranslate = this._translateX;
|
||||
var lastElement = this.buttonElements[this.buttonElements.length - 1];
|
||||
var firstElement = this.buttonElements[0];
|
||||
|
||||
if (listWidth < this._viewWidth) {
|
||||
// align to horizontal center if list width is smaller than the container
|
||||
return (this._viewWidth - listWidth) / 2 + this._margin;
|
||||
} else if (nodeLeft + nodeWidth >
|
||||
-this._translateX + this._viewWidth + this._margin) {
|
||||
// scroll left if the node falls beyond the right edge of container
|
||||
newTranslate = this._viewWidth - nodeLeft - nodeWidth + this._margin;
|
||||
} else if (nodeLeft < -this._translateX + this._margin) {
|
||||
// scroll right if the node falls beyond the left edge of container
|
||||
newTranslate = -nodeLeft + this._margin;
|
||||
}
|
||||
|
||||
|
||||
// If the new scroll offset contains first/last node, we have to align the
|
||||
// list to begin/end.
|
||||
if (lastElement.offsetLeft + lastElement.offsetWidth <=
|
||||
-newTranslate + this._viewWidth + this._margin) {
|
||||
return this._viewWidth + this._margin - lastElement.offsetLeft -
|
||||
lastElement.offsetWidth;
|
||||
} else if (firstElement.offsetLeft >= -newTranslate + this._margin) {
|
||||
return -firstElement.offsetLeft + this._margin;
|
||||
}
|
||||
return newTranslate;
|
||||
};
|
||||
|
||||
proto._getFocusedElement = function() {
|
||||
return this.buttonElements[this._focusedIndex];
|
||||
};
|
||||
|
||||
proto._focusContent = function() {
|
||||
var elem = this._getFocusedElement();
|
||||
this._scrollTo(elem);
|
||||
|
||||
// move focus to smart dialog while transition running
|
||||
if (elem.focus && (typeof elem.focus) === 'function') {
|
||||
elem.focus();
|
||||
}
|
||||
};
|
||||
|
||||
proto._blurContent = function() {
|
||||
var elem = this._getFocusedElement();
|
||||
if (elem.blur && (typeof elem.blur === 'function')) {
|
||||
elem.blur();
|
||||
}
|
||||
};
|
||||
|
||||
proto.stopKeyNavigation = function() {
|
||||
this.element.removeEventListener('keydown', this);
|
||||
};
|
||||
|
||||
proto.movePrevious = function() {
|
||||
if (this._focusedIndex < 1) {
|
||||
// Do nothing when focus index is at the first button element.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusedIndex--;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto.moveNext = function() {
|
||||
if (this._focusedIndex > this.buttonElements.length - 2) {
|
||||
// Do nothing when focus index is at the last button element
|
||||
return;
|
||||
}
|
||||
this._focusedIndex++;
|
||||
this.focus();
|
||||
};
|
||||
|
||||
proto._handleKeyEvent = function(e) {
|
||||
if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
|
||||
this.movePrevious();
|
||||
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
|
||||
this.moveNext();
|
||||
}
|
||||
};
|
||||
|
||||
proto.handleEvent = function(e) {
|
||||
this._handleKeyEvent(e);
|
||||
|
||||
switch(e.target) {
|
||||
case this.element:
|
||||
if (e.type === 'will-open' || e.type === 'will-close') {
|
||||
this.fireEvent('modal-dialog-' + e.type);
|
||||
} else if (e.type === 'opened') {
|
||||
this._scrollTo(this.buttonElements[0]);
|
||||
// Play bubble animation when the smart-dialog is opened
|
||||
this.smartBubble.play(this.buttonElements);
|
||||
} else if (e.type === 'closed') {
|
||||
// Do click actions when the smart-dialog is closed
|
||||
this.element.classList.remove('visible');
|
||||
if ((this._clickedIndex || this._clickedIndex === 0) &&
|
||||
this.buttonSettings[this._clickedIndex].onClick) {
|
||||
this.buttonSettings[this._clickedIndex].onClick(
|
||||
this.buttonSettings[this._clickedIndex]);
|
||||
this._clickedIndex = null;
|
||||
} else if (this.onCancel) {
|
||||
// ESC key enter
|
||||
this.onCancel();
|
||||
}
|
||||
this.isOpened = false;
|
||||
this.stopKeyNavigation();
|
||||
this.element.classList.remove('modal-dialog-opened');
|
||||
this.fireEvent('modal-dialog-closed');
|
||||
}
|
||||
break;
|
||||
case this.smartBubble:
|
||||
this.element.classList.add('modal-dialog-opened');
|
||||
this.startKeyNavigation();
|
||||
this.focus();
|
||||
this.fireEvent('modal-dialog-opened');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SmartModalDialog.prototype = proto;
|
||||
exports.SmartModalDialog = SmartModalDialog;
|
||||
|
||||
})(window);
|
||||
124
gaia/tv_apps/app-deck/bower_components/smart-modal-dialog/style.css
vendored
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
.modal-dialog > .outer-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal-dialog > .outer-container:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 0.1rem;
|
||||
margin-left: -0.1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-dialog .container {
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-message-container {
|
||||
width: 96rem;
|
||||
position: relative;
|
||||
margin: 0 auto 13rem auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-custom-group {
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
font-family: Fira Sans;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-custom-group.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 70rem;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group input {
|
||||
font-size: 3.8rem;
|
||||
font-size: 3.8rem;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group .focus {
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 0.4rem;
|
||||
margin: 0.4rem auto 0 auto;
|
||||
visibility: hidden;
|
||||
background: #00caf2;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group .clear:before {
|
||||
vertical-align: top;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group .clear {
|
||||
position: absolute;
|
||||
left: calc(100% - 4rem);
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
background: #999999;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group :focus ~ .focus {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal-dialog-input-group :focus ~ .clear,
|
||||
.modal-dialog-input-group :focus.clear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-message {
|
||||
font-size: 3.8rem;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
font-family: Fira Sans;
|
||||
font-weight: 100;
|
||||
margin-bottom: 3.4rem;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-message.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-button-group {
|
||||
white-space: nowrap;
|
||||
/* use inline-block to wrap content */
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.modal-dialog.modal-dialog-opened .modal-dialog-button-group {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-button-group > * {
|
||||
margin-right: 4.4rem;
|
||||
}
|
||||
|
||||
.modal-dialog .modal-dialog-button-group > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
98
gaia/tv_apps/app-deck/index.html
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Apps</title>
|
||||
|
||||
<!-- stylesheets of building blocks -->
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-button/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-dialog/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-bubbles/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-modal-dialog/style.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="style/app_deck.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/app_deck_list.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/smart-screen/navigation.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/smart-screen/icons.css">
|
||||
|
||||
<!-- default cards for first time launch -->
|
||||
<link rel="resource" type="application/json" href="shared/resources/default-cards.json">
|
||||
|
||||
<!-- For test purpose on phone only -->
|
||||
<script defer src="shared/js/gesture_detector.js"></script>
|
||||
<script defer src="shared/js/smart-screen/init_gesture.js"></script>
|
||||
|
||||
<!-- Shared libraries -->
|
||||
<script src="shared/js/l10n.js"></script>
|
||||
<script src="shared/js/manifest_helper.js"></script>
|
||||
<script defer src="bower_components/evt/index.js"></script>
|
||||
<script defer src="shared/js/async_semaphore.js"></script>
|
||||
<script defer src="shared/js/smart-screen/scrollable.js"></script>
|
||||
<script defer src="shared/js/smart-screen/spatial_navigator.js"></script>
|
||||
<script defer src="shared/js/smart-screen/key_navigation_adapter.js"></script>
|
||||
<script defer src="shared/js/smart-screen/shared_utils.js"></script>
|
||||
<script defer src="shared/js/smart-screen/applications.js"></script>
|
||||
<script defer src="shared/js/smart-screen/piped_promise.js"></script>
|
||||
<script defer src="shared/js/smart-screen/card_store.js"></script>
|
||||
<script defer src="shared/js/smart-screen/cards/card.js"></script>
|
||||
<script defer src="shared/js/smart-screen/cards/application.js"></script>
|
||||
<script defer src="shared/js/smart-screen/cards/deck.js"></script>
|
||||
<script defer src="shared/js/smart-screen/cards/folder.js"></script>
|
||||
<script defer src="shared/js/smart-screen/cards/app_bookmark.js"></script>
|
||||
<script defer src="shared/js/smart-screen/card_manager.js"></script>
|
||||
<script defer src="shared/js/smart-screen/bookmark_manager.js"></script>
|
||||
|
||||
<!-- building blocks -->
|
||||
<script defer src="bower_components/smart-button/script.js"></script>
|
||||
<script defer src="bower_components/smart-dialog/script.js"></script>
|
||||
<script defer src="bower_components/smart-bubbles/script.js"></script>
|
||||
<script defer src="bower_components/smart-modal-dialog/base-modal-dialog.js"></script>
|
||||
<script defer src="bower_components/smart-modal-dialog/smart-modal-dialog.js"></script>
|
||||
|
||||
<!-- Specific code -->
|
||||
<script defer src="js/promotion_list.js"></script>
|
||||
<script defer src="js/context_menu.js"></script>
|
||||
<script defer src="js/app_deck.js"></script>
|
||||
|
||||
<!-- L10n properties -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/app-deck.{locale}.properties"/>
|
||||
<link rel="localization" href="shared/locales/date/date.{locale}.properties"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<article id="main-section" contextmenu="context-menu">
|
||||
<section role="region" class="skin-dark" id="jumbotron">
|
||||
<!-- do not add hidden here because we need it visible to probe its size -->
|
||||
<div id="app-deck-animation-banner" class="app-deck-animation-banner">
|
||||
<div class="banner-background"></div>
|
||||
<div class="banner-text-container">
|
||||
<h1 class="banner-title"></h1>
|
||||
<div class="banner-description"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-deck-main-banner" class="app-deck-main-banner">
|
||||
<div class="banner-background"></div>
|
||||
<div class="banner-text-container">
|
||||
<h1 class="banner-title"></h1>
|
||||
<span class="banner-description"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-deck-list-frame">
|
||||
<div id="app-deck-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
<section role="region" class="skin-dark" id="app-deck-grid-view-container">
|
||||
<div id="app-deck-grid-view"></div>
|
||||
</section>
|
||||
<menu type="context" id="context-menu">
|
||||
<menuitem icon="style/icons/ic_pin.png" data-l10n-id="pin-to-home" id="pin-to-home"></menuitem>
|
||||
<menuitem icon="style/icons/ic_pin.png" data-l10n-id="unpin-from-home" id="unpin-from-home"></menuitem>
|
||||
<menuitem icon="style/icons/ic_delete.png" data-l10n-id="remove-card" id="remove-card"></menuitem>
|
||||
</menu>
|
||||
</article>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
462
gaia/tv_apps/app-deck/js/app_deck.js
Executable file
|
|
@ -0,0 +1,462 @@
|
|||
/* global SpatialNavigator, SharedUtils, Applications, URL, evt, XScrollable,
|
||||
KeyNavigationAdapter, ContextMenu, CardManager, PromotionList,
|
||||
BookmarkManager */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const ICON_SIZE = 180 * (window.devicePixelRatio || 1);
|
||||
const DEFAULT_ICON_PATH = 'style/icons/appic_developer.png';
|
||||
|
||||
/**
|
||||
* @class AppDeck
|
||||
*
|
||||
* @requires SpatialNavigator
|
||||
* @requires SharedUtils
|
||||
* @requires Applications
|
||||
* @requires {@link http://bit.ly/1DWJJmF|evt}
|
||||
* @requires XScrollable
|
||||
* @requires KeyNavigationAdapter
|
||||
* @requires {@link ContextMenu}
|
||||
* @requires CardManager
|
||||
* @requires {@link PromotionList}
|
||||
*
|
||||
* @fires AppDeck#focus
|
||||
*/
|
||||
var AppDeck = function() {
|
||||
};
|
||||
|
||||
AppDeck.prototype = evt({
|
||||
_navigableElements: [],
|
||||
|
||||
_spatialNavigator: undefined,
|
||||
|
||||
_keyNavigationAdapter: undefined,
|
||||
|
||||
_contextMenu: undefined,
|
||||
|
||||
_focusElem: undefined,
|
||||
|
||||
_appDeckGridViewElem: document.getElementById('app-deck-grid-view'),
|
||||
|
||||
_appDeckListScrollable: undefined,
|
||||
|
||||
_cardManager: undefined,
|
||||
|
||||
_bookmarkManager: undefined,
|
||||
|
||||
_outOfControlArea: true,
|
||||
|
||||
_disableNavigation: false,
|
||||
/**
|
||||
* Initialize AppDeck. This is the main entry of the whole app.
|
||||
*
|
||||
* @public
|
||||
* @method AppDeck#init
|
||||
*/
|
||||
init: function ad_init() {
|
||||
var that = this;
|
||||
this._keyNavigationAdapter = new KeyNavigationAdapter();
|
||||
this._keyNavigationAdapter.init();
|
||||
this._cardManager = new CardManager();
|
||||
// initialize CardManager in 'readonly' mode. Notice that only smart-home
|
||||
// could use 'readwrite' mode.
|
||||
this._cardManager.init('readonly').then(function() {
|
||||
that._cardManager.on('cardlist-changed',
|
||||
that.onCardListChanged.bind(that));
|
||||
});
|
||||
|
||||
this._bookmarkManager = BookmarkManager;
|
||||
this._bookmarkManager.init(null, 'readwrite');
|
||||
this._bookmarkManager.on('change', that.onBookmarkChanged.bind(that));
|
||||
|
||||
// Because module Applications use manifest helper to get localized app
|
||||
// name. We cannot initialize Applications until l10n is ready.
|
||||
// See bug 1170083.
|
||||
var afterApplicationsInit = function() {
|
||||
var apps = Applications.getAllAppEntries();
|
||||
var appGridElements = apps.map(that._createAppGridElement.bind(that));
|
||||
appGridElements.forEach(function(appGridElem) {
|
||||
that._appDeckGridViewElem.appendChild(appGridElem);
|
||||
});
|
||||
|
||||
// Add Bookmarks
|
||||
var bookmarkArr = [];
|
||||
that._bookmarkManager.iterate(function(bookmark) {
|
||||
bookmarkArr.push(bookmark);
|
||||
}).then(function() {
|
||||
bookmarkArr.sort((a, b) => a.date - b.date);
|
||||
bookmarkArr.forEach(bookmark => {
|
||||
var elem = that._createBookmarkGridElement(bookmark);
|
||||
that._appDeckGridViewElem.appendChild(elem);
|
||||
that._spatialNavigator.add(elem);
|
||||
});
|
||||
});
|
||||
|
||||
// promotion list must be created before XScrollable because it creates
|
||||
// elements for XScrollable.
|
||||
that._promotionList = new PromotionList();
|
||||
that._promotionList.init();
|
||||
|
||||
that._appDeckListScrollable = new XScrollable({
|
||||
frameElem: 'app-deck-list-frame',
|
||||
listElem: 'app-deck-list',
|
||||
itemClassName: 'app-deck-list-item',
|
||||
margin: 1.4
|
||||
});
|
||||
that._navigableElements =
|
||||
SharedUtils.nodeListToArray(
|
||||
document.querySelectorAll('.navigable:not(.app-banner)'))
|
||||
.concat(appGridElements);
|
||||
that._spatialNavigator = new SpatialNavigator(that._navigableElements);
|
||||
|
||||
that._keyNavigationAdapter.on('move', that.onMove.bind(that));
|
||||
// All behaviors which no need to have multple events while holding the
|
||||
// key should use keyup.
|
||||
that._keyNavigationAdapter.on('enter-keyup', that.onEnter.bind(that));
|
||||
that._spatialNavigator.on('focus', that.onFocus.bind(that));
|
||||
that._spatialNavigator.on('unfocus', that.onUnfocus.bind(that));
|
||||
that._appDeckListScrollable.on('focus',
|
||||
that.onFocusOnAppDeckListScrollable.bind(that));
|
||||
// start focusing on promotion list
|
||||
that._appDeckListScrollable.focus();
|
||||
that._contextMenu = new ContextMenu();
|
||||
that._contextMenu.init(that);
|
||||
Applications.on('install', that.onAppInstalled.bind(that));
|
||||
Applications.on('update', that.onAppUpdated.bind(that));
|
||||
Applications.on('uninstall', that.onAppUninstalled.bind(that));
|
||||
};
|
||||
|
||||
navigator.mozL10n.once(function() {
|
||||
Applications.init(afterApplicationsInit);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fills element with icon which is fetched from
|
||||
* [app](http://mzl.la/1DJP6oZ)
|
||||
*
|
||||
* @private
|
||||
* @method AppDeck#_fillAppButtonIcon
|
||||
* @param {DOMApplication} app - app instance where we fetch icon from,
|
||||
* see [here](http://mzl.la/1DJP6oZ)
|
||||
* @param {SmartButton} elem - [SmartButton](http://bit.ly/1Ld0WYX)
|
||||
* instance to be filled
|
||||
*/
|
||||
_fillAppButtonIcon: function ad_fillAppButtonIcon(app, elem) {
|
||||
Applications.getIconBlob(app.manifestURL, app.entryPoint, ICON_SIZE,
|
||||
function(blob) {
|
||||
var iconURL = blob ? URL.createObjectURL(blob) : DEFAULT_ICON_PATH;
|
||||
if (elem.dataset.revokableURL) {
|
||||
// make sure to revoke iconURL once it is no longer needed.
|
||||
// For example, icon is changed or app is uninstalled
|
||||
URL.revokeObjectURL(elem.dataset.revokableURL);
|
||||
}
|
||||
elem.dataset.revokableURL = blob ? iconURL : undefined;
|
||||
elem.style.backgroundImage = 'url("' + iconURL + '")';
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create SmartButton element based on input [app](http://mzl.la/1DJP6oZ)
|
||||
* instance
|
||||
*
|
||||
* @private
|
||||
* @method AppDeck#_createAppGridElement
|
||||
* @param {DOMApplication} app - [app](http://mzl.la/1DJP6oZ|)
|
||||
* instance
|
||||
* @return {SmartButton} - see [here](http://bit.ly/1Ld0WYX)
|
||||
*/
|
||||
_createAppGridElement: function ad_createAppGridElement(app) {
|
||||
var appButton = document.createElement('smart-button');
|
||||
appButton.dataset.manifestURL = app.manifestURL;
|
||||
appButton.dataset.entryPoint = app.entryPoint;
|
||||
appButton.dataset.name = app.name;
|
||||
appButton.dataset.removable = app.removable;
|
||||
appButton.setAttribute('type', 'app-button');
|
||||
appButton.setAttribute('app-type', 'app');
|
||||
appButton.classList.add('app-button');
|
||||
appButton.classList.add('navigable');
|
||||
appButton.setAttribute('label', app.name);
|
||||
this._fillAppButtonIcon(app, appButton);
|
||||
return appButton;
|
||||
},
|
||||
|
||||
_createBookmarkGridElement: function ad_createBookmarkElement(bookmark) {
|
||||
var bookmarkButton = document.createElement('smart-button');
|
||||
bookmarkButton.dataset.url = bookmark.url;
|
||||
bookmarkButton.dataset.name = bookmark.name;
|
||||
bookmarkButton.dataset.removable = true;
|
||||
bookmarkButton.setAttribute('type', 'app-button');
|
||||
bookmarkButton.setAttribute('app-type', 'bookmark');
|
||||
bookmarkButton.classList.add('app-button');
|
||||
bookmarkButton.classList.add('navigable');
|
||||
bookmarkButton.setAttribute('label', bookmark.name);
|
||||
|
||||
if(bookmark.icon) {
|
||||
var iconURL = URL.createObjectURL(bookmark.icon);
|
||||
bookmarkButton.dataset.revokableURL = iconURL;
|
||||
bookmarkButton.style.backgroundImage = 'url("' + iconURL + '")';
|
||||
}
|
||||
return bookmarkButton;
|
||||
},
|
||||
|
||||
onCardListChanged: function ad_onCardListChanged() {
|
||||
if (this._focusElem) {
|
||||
this.fireFocusEvent(this._focusElem);
|
||||
}
|
||||
},
|
||||
|
||||
onBookmarkChanged: function ad_onBookmarkChanged(evt) {
|
||||
var targetElem;
|
||||
switch (evt.operation) {
|
||||
case 'added':
|
||||
this._bookmarkManager.get(evt.id).then(bookmark => {
|
||||
targetElem = this._createBookmarkGridElement(bookmark);
|
||||
this._appDeckGridViewElem.appendChild(targetElem);
|
||||
this._spatialNavigator.add(targetElem);
|
||||
});
|
||||
break;
|
||||
case 'removed':
|
||||
targetElem = this._appDeckGridViewElem.querySelector(
|
||||
'smart-button[data-url="' + evt.id +'"]');
|
||||
// Move focus to next or previous element of `elem`, because
|
||||
// we are going to remove `elem` from DOM tree
|
||||
var nextFocus = targetElem.nextElementSibling ||
|
||||
targetElem.previousElementSibling;
|
||||
this._spatialNavigator.focus(nextFocus);
|
||||
|
||||
this._appDeckGridViewElem.removeChild(targetElem);
|
||||
this._spatialNavigator.remove(targetElem);
|
||||
URL.revokeObjectURL(targetElem.dataset.revokableURL);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify other modules about details of currently focused element.
|
||||
*
|
||||
* @public
|
||||
* @method AppDeck#fireFocusEvent
|
||||
* @param {HTMLElement} elem - currently focused element
|
||||
*/
|
||||
/**
|
||||
* This event is fired whenever the focus in AppDeck moves to a pinnable
|
||||
* element (For now it's an app or a bookmark).
|
||||
* @event AppDeck#focus
|
||||
* @type {Object}
|
||||
* @property {HTMLElement} elem - currently focused element
|
||||
* @property {Boolean} pinned - Has it been pinned to Home
|
||||
*/
|
||||
fireFocusEvent: function ad_fireFocusEvent(elem) {
|
||||
var that = this;
|
||||
var type = elem && elem.getAttribute('app-type');
|
||||
|
||||
var query;
|
||||
if (type === 'app') {
|
||||
query = {
|
||||
manifestURL: elem.dataset.manifestURL,
|
||||
entryPoint: elem.dataset.entryPoint
|
||||
};
|
||||
} else if (type === 'bookmark') {
|
||||
query = {
|
||||
url: elem.dataset.url
|
||||
};
|
||||
} else {
|
||||
// We have no other types for now.
|
||||
return;
|
||||
}
|
||||
|
||||
this._cardManager.isPinned(query).then(pinned => {
|
||||
that.fire('focus', {
|
||||
elem: elem,
|
||||
pinned: pinned
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onFocus: function ad_onFocus(elem) {
|
||||
// The _focusElement may be XScrollable. It doesn't have blur method. If
|
||||
// it has blur method, it should be DOM element.
|
||||
if (this._focusElem && this._focusElem.blur) {
|
||||
this._focusElem.blur();
|
||||
}
|
||||
|
||||
if (elem instanceof XScrollable) {
|
||||
// When we move focus from smart-button to scrollable,
|
||||
// we have to remember the last focused smart-button in app list. So
|
||||
// that, we can move focus back to app-deck-grid-view area afterwards.
|
||||
if (this._focusElem) {
|
||||
if (this._focusElem.nodeName === 'SMART-BUTTON') {
|
||||
this._lastFocusedSmartButton = this._focusElem;
|
||||
}
|
||||
}
|
||||
this._focusElem = elem;
|
||||
elem.focus();
|
||||
} else if (elem.nodeName) {
|
||||
|
||||
// When we move focus back to app-deck-grid-view area
|
||||
// (e.g. area contains smart-button), we should always focus on
|
||||
// last focused smart-button if there is any
|
||||
if (elem.nodeName === 'SMART-BUTTON' && this._lastFocusedSmartButton) {
|
||||
elem = this._lastFocusedSmartButton;
|
||||
this._spatialNavigator.focus(elem);
|
||||
}
|
||||
elem.focus();
|
||||
// Locate focused button to vertical middle of grid view. This way
|
||||
// buttons one row above would always slightly be on bottom of the
|
||||
// topmost scroll list (accroding to our layout). Thus we can prevent
|
||||
// jumping from second row to the scroll list when pressing up key.
|
||||
this._scrollTo(elem);
|
||||
this._focusElem = elem;
|
||||
}
|
||||
|
||||
this.fireFocusEvent(elem);
|
||||
},
|
||||
|
||||
_scrollTo: function ad_scrollTo(elem) {
|
||||
var scrollY = (elem.offsetTop - this._appDeckGridViewElem.offsetTop) -
|
||||
(this._appDeckGridViewElem.offsetHeight - elem.offsetHeight) / 2;
|
||||
this._appDeckGridViewElem.scrollTo(0, scrollY);
|
||||
},
|
||||
|
||||
onUnfocus: function ad_onUnfocus(elem) {
|
||||
// When we move focus from smart-button to another smart-button,
|
||||
// we don't have to remember last focused smart-button
|
||||
if (elem.nodeName === 'SMART-BUTTON') {
|
||||
this._lastFocusedSmartButton = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
onFocusOnAppDeckListScrollable:
|
||||
function ad_onFocusOnAppDeckListScrollable(scrollable, itemElem) {
|
||||
itemElem.focus();
|
||||
},
|
||||
|
||||
onEnter: function ad_onEnter() {
|
||||
if (this._disableNavigation) {
|
||||
return;
|
||||
}
|
||||
|
||||
var focused = this._spatialNavigator.getFocusedElement();
|
||||
if (focused && focused.dataset && focused.dataset.manifestURL) {
|
||||
Applications.launch(
|
||||
focused.dataset.manifestURL, focused.dataset.entryPoint);
|
||||
} else if (focused && focused.dataset && focused.dataset.url) {
|
||||
window.open(focused.dataset.url, '_blank', 'remote=true,applike=true');
|
||||
}
|
||||
},
|
||||
|
||||
onMove: function ad_onMove(key) {
|
||||
if (this._disableNavigation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When we are not in the SpatialNavigator's control area,
|
||||
// use XScrollable object's spatial navigator to move.
|
||||
// If it fails to move, and it's a 'down' operation, then we
|
||||
// move into the last focused element within SpatialNavigator's
|
||||
// control area.
|
||||
if (this._outOfControlArea) {
|
||||
if (!this._appDeckListScrollable.spatialNavigator.move(key) &&
|
||||
key == 'down') {
|
||||
this._spatialNavigator.focus(this._lastFocusedSmartButton);
|
||||
this._outOfControlArea = false;
|
||||
}
|
||||
} else {
|
||||
// Vice versa.
|
||||
if (!this._spatialNavigator.move(key) && key == 'up') {
|
||||
this._appDeckListScrollable.focus();
|
||||
this._outOfControlArea = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onAppInstalled: function ad_onAppInstalled(apps) {
|
||||
var that = this;
|
||||
var appGridElements = apps.map(this._createAppGridElement.bind(this));
|
||||
var firstBookmarkElem = this._appDeckGridViewElem.querySelector(
|
||||
'smart-button[app-type="bookmark"]');
|
||||
|
||||
appGridElements.forEach(function(elem) {
|
||||
that._appDeckGridViewElem.insertBefore(elem, firstBookmarkElem);
|
||||
that._spatialNavigator.add(elem);
|
||||
});
|
||||
},
|
||||
|
||||
onAppUpdated: function ad_onAppUpdated(apps) {
|
||||
var that = this;
|
||||
var appGridElements = apps.map(this._findAppGridElement.bind(this));
|
||||
appGridElements.forEach(function(elem, index) {
|
||||
var app = apps[index];
|
||||
elem.dataset.name = app.name;
|
||||
that._fillAppButtonIcon(app, elem);
|
||||
});
|
||||
},
|
||||
|
||||
onAppUninstalled: function ad_onAppUninstalled(apps) {
|
||||
var that = this;
|
||||
var appGridElements = apps.map(this._findAppGridElement.bind(this));
|
||||
appGridElements.forEach(function(elem) {
|
||||
if (elem.dataset.revokableURL) {
|
||||
URL.revokeObjectURL(elem.dataset.revokableURL);
|
||||
}
|
||||
// Move focus to next or previous element of `elem`, because
|
||||
// we are going to remove `elem` from DOM tree
|
||||
var nextFocus = elem.nextElementSibling || elem.previousElementSibling;
|
||||
that._spatialNavigator.focus(nextFocus);
|
||||
|
||||
that._spatialNavigator.remove(elem);
|
||||
that._appDeckGridViewElem.removeChild(elem);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Shorthand function to get all SmartButtons of apps
|
||||
*
|
||||
* @private
|
||||
* @method AppDeck#_findAllAppGridElements
|
||||
* @return {Array} array of SmartButton
|
||||
*/
|
||||
_findAllAppGridElements: function ad_findallAppGridElements() {
|
||||
return SharedUtils.nodeListToArray(
|
||||
document.getElementsByClassName('app-button'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Shorthand function to find specific SmartButton representing the app
|
||||
*
|
||||
* @private
|
||||
* @method AppDeck#_findAppGridElement
|
||||
* @param {DOMApplication} app the [app](http://mzl.la/1DJP6oZ) instance
|
||||
* which we want to find SmartButton is
|
||||
* represented for
|
||||
* @return {SmartButton}
|
||||
*/
|
||||
_findAppGridElement: function ad_findAppGridElement(app) {
|
||||
var elements = this._findAllAppGridElements();
|
||||
var found;
|
||||
elements.some(function(element) {
|
||||
if (element.dataset.manifestURL === app.manifestURL) {
|
||||
found = element;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return found;
|
||||
},
|
||||
|
||||
disableNavigation: function ad_stopNavigation() {
|
||||
this._disableNavigation = true;
|
||||
},
|
||||
|
||||
enableNavigation: function ad_stopNavigation() {
|
||||
this._disableNavigation = false;
|
||||
this._spatialNavigator.focus();
|
||||
}
|
||||
});
|
||||
|
||||
exports.appDeck = new AppDeck();
|
||||
exports.appDeck.init();
|
||||
|
||||
}(window));
|
||||
252
gaia/tv_apps/app-deck/js/context_menu.js
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
'use strict';
|
||||
|
||||
/* global MozActivity, Applications, BookmarkManager, SmartModalDialog */
|
||||
/* jshint nonew: false */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
|
||||
/**
|
||||
* It handles everything related to contextmenu in app-deck.
|
||||
*
|
||||
* @class ContextMenu
|
||||
* @requires Applications
|
||||
* @requires SharedUtils
|
||||
*/
|
||||
var ContextMenu = function() {
|
||||
};
|
||||
|
||||
ContextMenu.prototype = {
|
||||
pinToHomeElem: document.getElementById('pin-to-home'),
|
||||
unpinFromHomeElem: document.getElementById('unpin-from-home'),
|
||||
mainSection: document.getElementById('main-section'),
|
||||
removeElem: document.getElementById('remove-card'),
|
||||
contextMenuElem: document.getElementById('context-menu'),
|
||||
|
||||
_appDeck: undefined,
|
||||
_target: {},
|
||||
|
||||
|
||||
/**
|
||||
* Initialize ContextMenu
|
||||
*
|
||||
* @public
|
||||
* @method ContextMenu#init
|
||||
* @param {AppDeck} appDeck - instance of {@link AppDeck}
|
||||
*/
|
||||
init: function cm_init(appDeck) {
|
||||
this._appDeck = appDeck;
|
||||
this._appDeck.on('focus', this.onFocus.bind(this));
|
||||
|
||||
this._bookmarkManager = BookmarkManager;
|
||||
|
||||
this._modalDialog = new SmartModalDialog();
|
||||
|
||||
this.pinToHomeElem.addEventListener('click', this.pinOrUnpin.bind(this));
|
||||
this.unpinFromHomeElem.addEventListener(
|
||||
'click', this.pinOrUnpin.bind(this));
|
||||
|
||||
this.removeElem.addEventListener('click', this.onRemove.bind(this));
|
||||
|
||||
this._modalDialog.container.addEventListener('modal-dialog-closed',
|
||||
this._appDeck.enableNavigation.bind(this._appDeck));
|
||||
this._modalDialog.container.addEventListener('modal-dialog-will-open',
|
||||
this._appDeck.disableNavigation.bind(this._appDeck));
|
||||
},
|
||||
|
||||
_selfApp: undefined,
|
||||
|
||||
/**
|
||||
* Underlying function which actual send string message on 'appdeck-channel'
|
||||
*
|
||||
* @private
|
||||
* @method ContextMenu#_connectAndSend
|
||||
* @param {String} message - the message which will be sent on
|
||||
* 'appdeck-channel'
|
||||
*/
|
||||
_connectAndSend: function cm_sendMessage(message) {
|
||||
this._selfApp.connect('appdeck-channel').then(function (ports) {
|
||||
ports.forEach(function(port) {
|
||||
port.postMessage(message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send message on 'appdeck-channel' using Inter-App Communication.
|
||||
* smart-home will monitor this channel. Currently we use this channel to
|
||||
* tell smart-home to what app we would like to unpin.
|
||||
*
|
||||
* @public
|
||||
* @method ContextMenu#sendMessage
|
||||
* @param {String} type - message type, which must be 'unpin' for now.
|
||||
* @param {Object} data - message data in JSON object format
|
||||
*/
|
||||
sendMessage: function cm_sendMessage(type, data) {
|
||||
var that = this;
|
||||
var message = {
|
||||
type: type,
|
||||
data: data
|
||||
};
|
||||
|
||||
if (this._selfApp) {
|
||||
this._connectAndSend(message);
|
||||
} else {
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
that._selfApp = evt.target.result;
|
||||
that._connectAndSend(message);
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* To tell smart-home what app we would like to unpin
|
||||
*
|
||||
* @private
|
||||
* @method ContextMetnu#_sendUnpinMessage
|
||||
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
|
||||
*/
|
||||
_sendUnpinMessage: function cm_sendUnpinMessage(cardElem) {
|
||||
var type = cardElem.getAttribute('app-type');
|
||||
if (type === 'app') {
|
||||
// Notice that here we didn't specify launchURL because we assume all
|
||||
// 'Application' in app-deck are launched by calling app.launch().
|
||||
// However, in the case of other deck-like app, if we are going to pin
|
||||
// application which is launched by different launchURL, we must specify
|
||||
// 'launchURL' here.
|
||||
this.sendMessage('unpin', {
|
||||
manifestURL: cardElem.dataset.manifestURL
|
||||
});
|
||||
} else if (type === 'bookmark') {
|
||||
this.sendMessage('unpin', {
|
||||
url: cardElem.dataset.url
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Based on currently focused SmartButton, we'll decide we are going to
|
||||
* pin or unpin the app. This function is invoked when we get 'click' event
|
||||
* on pinToHomeElem. Notice that we use `MozActivity` to pin app because we
|
||||
* want to bring users to Home when they finish pinning. However we unpin
|
||||
* app via IAC because we want users stay in AppDeck after unpinning.
|
||||
*
|
||||
* @public
|
||||
* @method ContextMenu#pinOrUnpin
|
||||
*/
|
||||
pinOrUnpin: function cm_pinOrUnpin() {
|
||||
var cardElem = this._target.elem;
|
||||
var type = this._getTargetType();
|
||||
var dataset = cardElem.dataset;
|
||||
|
||||
if (this._target.pinned) {
|
||||
this._sendUnpinMessage(this._target.elem);
|
||||
} else if (type === 'app') {
|
||||
// XXX: preferredSize should be determined by
|
||||
// real offsetWidth of cardThumbnailElem in smart-home instead of
|
||||
// hard-coded value
|
||||
Applications.getIconBlob(
|
||||
dataset.manifestURL, dataset.entryPoint, 354, blob => {
|
||||
// Notice that here we didn't specify launchURL because we assume
|
||||
// all 'Application' in app-deck are launched by calling
|
||||
// app.launch().
|
||||
// However, in the case of other deck-like app, if we are going to
|
||||
// pin application which is launched by different launchURL, we
|
||||
// must specify 'launchURL' here.
|
||||
new MozActivity({
|
||||
name: 'pin',
|
||||
data: {
|
||||
type: 'Application',
|
||||
group: 'application',
|
||||
manifestURL: dataset.manifestURL,
|
||||
// We use app's original icon instead of screenshot here
|
||||
// because we are in app deck. For the case of getting
|
||||
// screenshot, please refer to bug 1100238.
|
||||
thumbnail: blob
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (type === 'bookmark') {
|
||||
this._bookmarkManager.get(dataset.url).then(card => {
|
||||
new MozActivity({
|
||||
name: 'pin',
|
||||
data: {
|
||||
name: {raw: card.name},
|
||||
type: 'AppBookmark',
|
||||
group: 'application',
|
||||
url: card.url,
|
||||
thumbnail: card.icon
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRemove: function cm_onRemove() {
|
||||
var type = this._getTargetType();
|
||||
var elem = this._target.elem;
|
||||
if (type === 'app') {
|
||||
this.uninstall(elem);
|
||||
|
||||
} else if (type === 'bookmark') {
|
||||
this._appDeck.disableNavigation();
|
||||
this._modalDialog.open({
|
||||
message: {
|
||||
textL10nId: {
|
||||
id: 'delete-bookmark-alert',
|
||||
args: {
|
||||
name: elem.getAttribute('label')
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonSettings: [
|
||||
{
|
||||
textL10nId: 'cancel',
|
||||
defaultFocus: true,
|
||||
},
|
||||
{
|
||||
textL10nId: 'delete',
|
||||
class: 'danger',
|
||||
onClick: () => {
|
||||
this._bookmarkManager.remove(elem.dataset.url);
|
||||
this._sendUnpinMessage(elem);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_getTargetType: function cm_getType() {
|
||||
return this._target.elem && this._target.elem.getAttribute('app-type');
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninstall app represented by current focused SmartButton
|
||||
* @public
|
||||
* @method ContextMenu#uninstall
|
||||
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
|
||||
*/
|
||||
uninstall: function cm_uninstall(cardElem) {
|
||||
var appInstance =
|
||||
Applications.installedApps[cardElem.dataset.manifestURL];
|
||||
if (appInstance) {
|
||||
navigator.mozApps.mgmt.uninstall(appInstance);
|
||||
}
|
||||
},
|
||||
|
||||
onFocus: function cm_onFocus(target) {
|
||||
this._target = target;
|
||||
|
||||
this.contextMenuElem.innerHTML = '';
|
||||
if (target.pinned) {
|
||||
this.contextMenuElem.appendChild(this.unpinFromHomeElem);
|
||||
} else {
|
||||
this.contextMenuElem.appendChild(this.pinToHomeElem);
|
||||
}
|
||||
this.contextMenuElem.appendChild(this.removeElem);
|
||||
}
|
||||
};
|
||||
|
||||
exports.ContextMenu = ContextMenu;
|
||||
})(window);
|
||||
213
gaia/tv_apps/app-deck/js/promotion_list.js
Executable file
|
|
@ -0,0 +1,213 @@
|
|||
/* global MozActivity, KeyEvent */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const ANIMATION_TIMER = 400;
|
||||
|
||||
var TEST_LIST = [
|
||||
{
|
||||
'imageUrl': 'style/banners/firefox-os.png',
|
||||
'title': 'Firefox OS',
|
||||
'description': 'Learn more about Firefox OS.',
|
||||
'url': 'https://www.mozilla.org/en-US/firefox/os/'
|
||||
}, {
|
||||
'imageUrl': 'style/banners/participation.png',
|
||||
'title': 'Firefox OS Smart TV Participation',
|
||||
'description': 'Learn how you can participate!',
|
||||
'url': 'https://firefoxos.mozilla.community/fxos-tv/'
|
||||
}, {
|
||||
'imageUrl': 'style/banners/mdn.png',
|
||||
'title': 'MDN',
|
||||
'description': 'Learn about TV apps developement!',
|
||||
'url': 'http://mzl.la/1NFslRZ'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* PromotionList controls everything on the upper half of AppDeck
|
||||
* @class PromotionList
|
||||
*/
|
||||
function PromotionList() {}
|
||||
|
||||
var proto = PromotionList.prototype;
|
||||
|
||||
proto.init = function() {
|
||||
this.bannerContainer = document.getElementById('app-deck-list');
|
||||
this.container = document.getElementById('app-deck-main-banner');
|
||||
this.animator = document.getElementById('app-deck-animation-banner');
|
||||
|
||||
this.animator.addEventListener('transitionend', this);
|
||||
|
||||
this._initBannerSize(this.animator);
|
||||
this._initBannerSize(this.container);
|
||||
|
||||
this.animator.classList.add('hidden');
|
||||
|
||||
// load test content.
|
||||
TEST_LIST.forEach(this._createBanner.bind(this));
|
||||
// set first one as context item.
|
||||
this.setContextItem(0);
|
||||
};
|
||||
|
||||
proto._createBanner = function(item, index) {
|
||||
var div = document.createElement('div');
|
||||
div.classList.add('app-banner');
|
||||
|
||||
var btn = document.createElement('smart-button');
|
||||
btn.setAttribute('type', 'banner-button');
|
||||
btn.classList.add('app-deck-list-item');
|
||||
btn.style.backgroundImage = 'url(\'' + item.imageUrl + '\'), ' +
|
||||
'url(\'style/icons/ic_default_app.png\')';
|
||||
btn.dataset.index = index;
|
||||
|
||||
btn.addEventListener('focus', this);
|
||||
btn.addEventListener('keyup', this);
|
||||
|
||||
div.appendChild(btn);
|
||||
this.bannerContainer.appendChild(div);
|
||||
};
|
||||
|
||||
proto._initBannerSize = function pl_initBannerSize(container) {
|
||||
var bg = container.querySelector('.banner-background');
|
||||
var text = container.querySelector('.banner-text-container');
|
||||
|
||||
text.style.width = bg.style.width = container.clientWidth + 'px';
|
||||
};
|
||||
|
||||
proto._fillMainBanner = function pl_fillMainBanner(container, item) {
|
||||
var bg = container.querySelector('.banner-background');
|
||||
var title = container.querySelector('.banner-title');
|
||||
var desc = container.querySelector('.banner-description');
|
||||
bg.style.backgroundImage = 'url(\'' + item.imageUrl + '\')';
|
||||
title.textContent = item.title;
|
||||
desc.textContent = item.description;
|
||||
};
|
||||
|
||||
proto._slideItemIn = function pl_slideItemIn(item) {
|
||||
// fill animation and start to slide
|
||||
this._fillMainBanner(this.animator, item);
|
||||
// cancel previous one if we already have it.
|
||||
this._cancelSliding();
|
||||
|
||||
// remove hidden and add standby to prepare for the startup of animation.
|
||||
this.animator.classList.remove('hidden');
|
||||
this.animator.classList.add('standby');
|
||||
|
||||
this._animationTimeout = setTimeout(function() {
|
||||
this._contextItem = item;
|
||||
this.animationTimeout = 0;
|
||||
this.animator.classList.remove('standby');
|
||||
this.animator.classList.add('sliding');
|
||||
this.container.classList.add('sliding');
|
||||
}.bind(this), ANIMATION_TIMER);
|
||||
};
|
||||
|
||||
proto._cancelSliding = function pl_cancelSliding() {
|
||||
if (this._animationTimeout) {
|
||||
clearTimeout(this._animationTimeout);
|
||||
this._animationTimeout = 0;
|
||||
// If _animationTimeout is not 0, it means the animation is prepared but
|
||||
// is not triggered. So, we need to rollback the state while clear the
|
||||
// timer.
|
||||
this.animator.classList.remove('standby');
|
||||
this.animator.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
proto.setContextItem = function pl_setContextItem(idx) {
|
||||
var item = TEST_LIST[idx];
|
||||
|
||||
if (!this._contextItem) {
|
||||
// If context item is undefined or null, it's first context item, we just
|
||||
// set it without animation.
|
||||
this._contextItem = item;
|
||||
this._fillMainBanner(this.container, item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._contextItem === item) {
|
||||
// If the context item is self, we don't need to slide anything and should
|
||||
// clear all pending or sliding timer.
|
||||
if (this.animator.classList.contains('sliding')) {
|
||||
// If _contextItem is sliding now, we should clear the pending animation
|
||||
// which means user presses key back and forward quickly.
|
||||
this._pendingAnimationItem = null;
|
||||
} else {
|
||||
// We still need to clear the sliding timer because the waiting item may
|
||||
// not be current item.
|
||||
this._cancelSliding();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.animator.classList.contains('sliding')) {
|
||||
this._pendingAnimationItem = item;
|
||||
return;
|
||||
}
|
||||
this._slideItemIn(item);
|
||||
};
|
||||
|
||||
proto._handleFocusChange = function(evt) {
|
||||
if (evt.target.dataset.index === null ||
|
||||
typeof(evt.target.dataset.index) === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setContextItem(evt.target.dataset.index);
|
||||
};
|
||||
|
||||
proto._openLink = function(evt) {
|
||||
var idx = evt.target.dataset.index;
|
||||
var url = TEST_LIST[idx].url;
|
||||
|
||||
// Ask browser app to open this link.
|
||||
var req = new MozActivity({
|
||||
name: 'view',
|
||||
data: {type: 'url', url: url}
|
||||
});
|
||||
req.onerror = function() {
|
||||
console.log(req.error.name);
|
||||
};
|
||||
};
|
||||
|
||||
proto._handleTransitionEnd = function(evt) {
|
||||
if (evt.propertyName !== 'transform' ||
|
||||
evt.currentTarget !== this.animator) {
|
||||
return;
|
||||
}
|
||||
// banner-background is the last one to move. Once it is moved, we should
|
||||
// set all data to main container.
|
||||
if (evt.target.classList.contains('banner-background')) {
|
||||
this.animator.classList.add('hidden');
|
||||
this.animator.classList.remove('sliding');
|
||||
this.container.classList.remove('sliding');
|
||||
this._fillMainBanner(this.container, this._contextItem);
|
||||
|
||||
if (this._pendingAnimationItem) {
|
||||
this._slideItemIn(this._pendingAnimationItem);
|
||||
this._pendingAnimationItem = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
proto.handleEvent = function(evt) {
|
||||
switch(evt.type) {
|
||||
case 'focus':
|
||||
this._handleFocusChange(evt);
|
||||
break;
|
||||
case 'transitionend':
|
||||
this._handleTransitionEnd(evt);
|
||||
break;
|
||||
case 'keyup':
|
||||
if (evt.keyCode != KeyEvent.DOM_VK_RETURN) {
|
||||
return;
|
||||
}
|
||||
this._openLink(evt);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
exports.PromotionList = PromotionList;
|
||||
|
||||
})(window);
|
||||
18
gaia/tv_apps/app-deck/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "app-deck",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["tv_apps/app-deck/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/app-deck"
|
||||
}
|
||||
}
|
||||
10
gaia/tv_apps/app-deck/locales/app-deck.en-US.properties
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
pin-to-home=Pin to Home
|
||||
pin-to-home.label=Pin to Home
|
||||
unpin-from-home=Unpin From Home
|
||||
unpin-from-home.label=Unpin From Home
|
||||
remove-card=Remove
|
||||
remove-card.label=Remove
|
||||
|
||||
delete-bookmark-alert={{name}} and all of its data will be deleted.
|
||||
delete=Delete
|
||||
cancel=Cancel
|
||||
41
gaia/tv_apps/app-deck/manifest.webapp
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "Apps",
|
||||
"description": "Smart Screen Apps Deck",
|
||||
"launch_path": "/index.html",
|
||||
"type": "certified",
|
||||
"role": "deck",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"en-US": {
|
||||
"name": "Apps",
|
||||
"description": "Smart Screen Apps Deck"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"354": "/style/icons/app-deck_354.png",
|
||||
"708": "/style/icons/app-deck_708.png"
|
||||
},
|
||||
"orientation": "landscape",
|
||||
"permissions": {
|
||||
"webapps-manage": {},
|
||||
"systemXHR": {},
|
||||
"open-remote-window": {},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {}
|
||||
},
|
||||
"datastores-access": {
|
||||
"home_cards": {
|
||||
"access": "readonly",
|
||||
"description": "Read card list"
|
||||
}
|
||||
},
|
||||
"datastores-owned": {
|
||||
"appdeck_bookmarks": {
|
||||
"access": "readwrite",
|
||||
"description": "Saves bookmarks from marketplace"
|
||||
}
|
||||
}
|
||||
}
|
||||
76
gaia/tv_apps/app-deck/style/app_deck.css
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
html {
|
||||
font-size: 10px;
|
||||
/* We have Fira Sans as default font in real device. But it isn't default in
|
||||
* browser. That's the main reason this line is here.*/
|
||||
font-family: "Fira Sans";
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
a:hover, a:active, a:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
#main-section {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-deck-grid-view-container {
|
||||
width: 100%;
|
||||
height: calc(100% - 46.6rem);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
#app-deck-grid-view {
|
||||
/* top = (6 - 5.8)rem */
|
||||
padding: 0.2rem 14.2rem 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
#app-deck-grid-view:after {
|
||||
content: "";
|
||||
display: block;
|
||||
/* app-deck-grid-view has padding-top 0.2rem, and the buttons in the first row
|
||||
have margin-top 5.8rem, so app-deck-grid-view bottom should have height
|
||||
5.8 + 0.2 - 2.9(button margin-bottom) = 3.1rem */
|
||||
height: 3.1rem;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"] {
|
||||
width: 18rem;
|
||||
height: 18rem;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
margin: 5.8rem 2.6rem 2.9rem 2.6rem;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
/** we use :after as label */
|
||||
smart-button[type="app-button"]:after {
|
||||
width: 18rem;
|
||||
height: 5rem;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
152
gaia/tv_apps/app-deck/style/app_deck_list.css
Executable file
|
|
@ -0,0 +1,152 @@
|
|||
|
||||
#jumbotron {
|
||||
width: 100%;
|
||||
height: 46.6rem;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
background-color: #323232;
|
||||
}
|
||||
|
||||
.app-deck-main-banner, .app-deck-animation-banner {
|
||||
position: absolute;
|
||||
left: -1rem;
|
||||
top: -1rem;
|
||||
width: calc(100% + 2rem);
|
||||
height: calc(100% + 2rem);
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.standby .banner-background,
|
||||
.app-deck-animation-banner.standby .banner-title,
|
||||
.app-deck-animation-banner.standby .banner-description {
|
||||
transform: translate(12rem, 0);
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.sliding .banner-background,
|
||||
.app-deck-animation-banner.sliding .banner-title,
|
||||
.app-deck-animation-banner.sliding .banner-description {
|
||||
transform: translate(0, 0);
|
||||
transition-property: transform;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
|
||||
transition-duration: 0.66s;
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.sliding .banner-description {
|
||||
transition-delay: 0.0s;
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.sliding .banner-title {
|
||||
transition-delay: 0.06s;
|
||||
}
|
||||
|
||||
.app-deck-animation-banner.sliding .banner-background {
|
||||
transition-delay: 0.12s;
|
||||
}
|
||||
|
||||
.app-deck-main-banner.sliding {
|
||||
width: 0;
|
||||
transition-property: width;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
|
||||
transition-duration: 0.66s;
|
||||
overflow: hidden;
|
||||
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.banner-background {
|
||||
position: absolute;
|
||||
/* to have slide-in transition work, we need to write a width to banner,
|
||||
like => width: 194rem;. This task will be done at JavaScript in runtime. */
|
||||
height: 100%;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
.banner-background:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.banner-text-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/* 24rem = 21.8rem banner list + 2.2rem spacing */
|
||||
bottom: 24rem;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
/* to have slide-in transition work, we need to write a width to banner,
|
||||
like => width: 194rem;. This task will be done at JavaScript in runtime. */
|
||||
|
||||
/* 15rem = 7.2 header + 7.2 desc + 0.6 spacing */
|
||||
max-height: 15rem;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
width: 120rem;
|
||||
height: 7.2rem;
|
||||
line-height: 7.2rem;
|
||||
font-size: 5.2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.banner-description {
|
||||
display: inline-block;
|
||||
|
||||
color: #ffffff;
|
||||
font-size: 2.4rem;
|
||||
font-weight: bold;
|
||||
line-height: 3.6rem;
|
||||
|
||||
width: 120rem;
|
||||
max-height: 7.2rem;
|
||||
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
#app-deck-list-frame {
|
||||
position: absolute;
|
||||
top: 22.6rem;
|
||||
width: 100%;
|
||||
height: 21.8rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-deck-list {
|
||||
position: relative;
|
||||
transition: transform 0.2s ease;
|
||||
transform-origin: 0 50%;
|
||||
}
|
||||
|
||||
.app-banner {
|
||||
display: inline-block;
|
||||
width: 38.4rem;
|
||||
height: 21.8rem;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
smart-button.app-deck-list-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center center, center center;
|
||||
background-size: 120% auto, 12rem auto;
|
||||
}
|
||||
BIN
gaia/tv_apps/app-deck/style/banners/firefox-os.png
Executable file
|
After Width: | Height: | Size: 37 KiB |
BIN
gaia/tv_apps/app-deck/style/banners/mdn.png
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
gaia/tv_apps/app-deck/style/banners/participation.png
Executable file
|
After Width: | Height: | Size: 40 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/Blank.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/app-deck_354.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/app-deck_708.png
Executable file
|
After Width: | Height: | Size: 31 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/appic_developer.png
Executable file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/appic_developer@2x.png
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_default_app.png
Executable file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_default_app@2x.png
Executable file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_delete.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_delete@2x.png
Executable file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_pin.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
gaia/tv_apps/app-deck/style/icons/ic_pin@2x.png
Executable file
|
After Width: | Height: | Size: 2.3 KiB |
21
gaia/tv_apps/browser/bower.json
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "browser",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/tv_apps/browser",
|
||||
"description": "Smart Screen Browser App",
|
||||
"main": "index.html",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"evt": "fxos-components/evt#~0.4.0",
|
||||
"smart-button": "fxos-components/smart-button",
|
||||
"smart-icons": "smart-components/smart-icons#~0.0.3"
|
||||
}
|
||||
}
|
||||
28
gaia/tv_apps/browser/bower_components/evt/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"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"
|
||||
],
|
||||
"_release": "0.4.0",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.4.0",
|
||||
"commit": "3593dd6b4dd19f683d00fd0eaf2581c11a128da6"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/evt.git",
|
||||
"_target": "~0.4.0",
|
||||
"_originalSource": "gaia-components/evt"
|
||||
}
|
||||
19
gaia/tv_apps/browser/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/tv_apps/browser/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;
|
||||
}
|
||||
|
||||
})();
|
||||
26
gaia/tv_apps/browser/bower_components/smart-button/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "smart-button",
|
||||
"version": "0.0.2",
|
||||
"homepage": "https://github.com/gaia-components/smart-button",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"_release": "0.0.2",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.0.2",
|
||||
"commit": "04fdae935e2f2ba99d1c4f3e156d17f12eb2d142"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/smart-button.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "gaia-components/smart-button"
|
||||
}
|
||||
17
gaia/tv_apps/browser/bower_components/smart-button/bower.json
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "smart-button",
|
||||
"version": "0.0.2",
|
||||
"homepage": "https://github.com/gaia-components/smart-button",
|
||||
"main": "script.js",
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"examples",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
62
gaia/tv_apps/browser/bower_components/smart-button/script.js
vendored
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
/* global KeyEvent */
|
||||
|
||||
window.SmartButton = (function(win) {
|
||||
// Extend from the HTMLButtonElement prototype
|
||||
var proto = Object.create(HTMLButtonElement.prototype);
|
||||
|
||||
proto.createdCallback = function() {
|
||||
this.addEventListener('mousedown', this);
|
||||
this.addEventListener('mouseup', this);
|
||||
this.addEventListener('touchstart', this);
|
||||
this.addEventListener('touchend', this);
|
||||
this.addEventListener('keydown', this);
|
||||
this.addEventListener('keyup', this);
|
||||
this.addEventListener('focus', this);
|
||||
this.addEventListener('blur', this);
|
||||
this.addEventListener('transitionend', this);
|
||||
this.tabIndex = 0;
|
||||
};
|
||||
|
||||
proto.handleEvent = function(evt) {
|
||||
switch(evt.type) {
|
||||
case 'mousedown':
|
||||
case 'touchstart':
|
||||
this.classList.add('pressed');
|
||||
break;
|
||||
case 'keydown':
|
||||
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
|
||||
this.classList.add('pressed');
|
||||
}
|
||||
break;
|
||||
case 'mouseup':
|
||||
case 'touchend':
|
||||
this.classList.remove('pressed');
|
||||
this.classList.add('released');
|
||||
break;
|
||||
case 'keyup':
|
||||
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
|
||||
this.classList.remove('pressed');
|
||||
this.classList.add('released');
|
||||
this.click();
|
||||
}
|
||||
break;
|
||||
case 'transitionend':
|
||||
if (this.classList.contains('released')) {
|
||||
this.classList.remove('released');
|
||||
}
|
||||
break;
|
||||
case 'focus':
|
||||
this.classList.add('focused');
|
||||
break;
|
||||
case 'blur':
|
||||
this.classList.remove('pressed');
|
||||
this.classList.remove('released');
|
||||
this.classList.remove('focused');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('smart-button', { prototype: proto });
|
||||
})(window);
|
||||
497
gaia/tv_apps/browser/bower_components/smart-button/style.css
vendored
Executable file
|
|
@ -0,0 +1,497 @@
|
|||
/**
|
||||
* Button state:
|
||||
* 1. normal
|
||||
* In this state, we don't add any class.
|
||||
* 2. focused (class name: focused)
|
||||
* The focused state may be triggered by keyboard or touch/mouse.
|
||||
* 3. pressed (class name: pressed)
|
||||
* This state is triggered by user pressed confirm button, mousedown,
|
||||
* and touchdown.
|
||||
* 4. released (class name: released)
|
||||
* This state is only for changing duration of transition. We enter this
|
||||
* state when user release the button. After transitionend, we goes back
|
||||
* to focused state.
|
||||
**/
|
||||
smart-button {
|
||||
font-family: "Fira Sans";
|
||||
display: inline-block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
/* we need to set border to none to remove the button border */
|
||||
border: none;
|
||||
/* the radius is still make button as round even if we don't have border */
|
||||
border-radius: 50%;
|
||||
/* 50% is not a real circle because of rounding, we have to use a value larger
|
||||
than 50% */
|
||||
-moz-outline-radius: 60%;
|
||||
outline: transparent 0 solid;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 5.4rem auto;
|
||||
background-position: center center;
|
||||
font-style: italic;
|
||||
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
|
||||
transition-duration: 0.42s;
|
||||
}
|
||||
|
||||
smart-button.focused {
|
||||
outline: 0;
|
||||
background-color: #ffffff;
|
||||
transform: scale(1.2);
|
||||
transition-duration: 0.42s;
|
||||
}
|
||||
|
||||
smart-button.released {
|
||||
transition-duration: 0.16s;
|
||||
}
|
||||
|
||||
smart-button.toggled {
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button.pressed {
|
||||
background-color: #00caf2;
|
||||
transform: scale(0.8);
|
||||
transition-duration: 0.06s;
|
||||
}
|
||||
|
||||
smart-button.disabled {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
smart-button[data-icon]:before {
|
||||
width: 5.4rem;
|
||||
left: 1.3rem;
|
||||
font-size: 5.4rem;
|
||||
line-height: 8rem;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
white-space: normal;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
smart-button[data-icon].focused:before {
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[data-icon].toggled:before {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[data-icon].pressed:before {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
smart-button[data-icon].disabled:before {
|
||||
color: #ffffff;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Text Button
|
||||
**/
|
||||
smart-button[type="simple-text"] {
|
||||
color: #dddddd;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
width: auto;
|
||||
height: 8rem;
|
||||
|
||||
line-height: 8rem;
|
||||
font-size: 2.4rem;
|
||||
-moz-user-select: none;
|
||||
padding-left: 2.6rem;
|
||||
padding-right: 2.6rem;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].focused {
|
||||
color: #ffffff;
|
||||
background: none;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].toggled {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="simple-text"].pressed {
|
||||
color: #00caf2;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of Simple Text Button
|
||||
**/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* underlined text button
|
||||
*/
|
||||
smart-button[type="underlined-text"] {
|
||||
position: relative;
|
||||
color: white;
|
||||
font-size: 2.4rem;
|
||||
padding-left: 3.8rem;
|
||||
padding-right: 3.8rem;
|
||||
border-radius: 4rem;
|
||||
width: auto;
|
||||
line-height: 8rem;
|
||||
margin-right: 1.4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"]:after {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
border-bottom: 0.6rem #61acd7 solid;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[type="underlined-text"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of underlined text button
|
||||
*/
|
||||
|
||||
/**
|
||||
* App Icons
|
||||
**/
|
||||
smart-button[type="app-button"] {
|
||||
/* To use 33.4rem instead of to use 33.6rem is to work around the anti-alias
|
||||
pixel. So, the circle is filled by the background almost totally. */
|
||||
width: 33.4rem;
|
||||
height: 33.4rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].focused {
|
||||
transform: scale(1.4);
|
||||
/* To use outline will not change the size of button and make the whole button
|
||||
thrilling. */
|
||||
outline: 0.8rem solid #ffffff;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].pressed {
|
||||
/* While scaling, we may have a gap between outline and icon which may be
|
||||
anti-alias pixel. So, we use scale(1.22) to prevent such kind of case */
|
||||
transform: scale(1.22);
|
||||
/* To use outline will not change the size of button and make the whole button
|
||||
thrilling. */
|
||||
outline: 1.0rem solid #ffffff;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].fullsized {
|
||||
background-size: 33.6rem auto;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"].fitted {
|
||||
background-size: 22rem auto;
|
||||
}
|
||||
|
||||
/** we use :after as label */
|
||||
smart-button[type="app-button"]:after {
|
||||
content: attr(label);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
right: auto;
|
||||
|
||||
width: 33.6rem;
|
||||
height: 8rem;
|
||||
|
||||
padding-top: 1rem;
|
||||
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
|
||||
font-size: 2.8rem;
|
||||
font-style: italic;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* text backgorund color
|
||||
**/
|
||||
smart-button[type="app-button"][app-type="deck"]:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="tv"]:after {
|
||||
background-color: #d90c83;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="app"]:after {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="device"]:after {
|
||||
background-color: #4a82ea;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="folder"] {
|
||||
background-color: #595959;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"]:before {
|
||||
left: 0;
|
||||
line-height: 30rem;
|
||||
font-size: 16rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
smart-button[type="app-button"][app-type="folder"]:before {
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of App Icons
|
||||
**/
|
||||
|
||||
/**
|
||||
* text and icon button
|
||||
*/
|
||||
smart-button[type="text-and-icon"] {
|
||||
position: relative;
|
||||
color: white;
|
||||
font-size: 2.4rem;
|
||||
padding-left: 4.6rem;
|
||||
padding-right: 4.6rem;
|
||||
border-radius: 4rem;
|
||||
width: auto;
|
||||
line-height: 8rem;
|
||||
margin-right: 1.4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"][data-icon] {
|
||||
padding-left: 9.6rem;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"][data-icon]:before {
|
||||
left: 3.8rem;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
}
|
||||
|
||||
smart-button[type="text-and-icon"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of text and icon button
|
||||
*/
|
||||
|
||||
/**
|
||||
* banner button
|
||||
**/
|
||||
smart-button[type="banner-button"] {
|
||||
position: relative;
|
||||
|
||||
color: #ffffff;
|
||||
font-size: 2rem;
|
||||
|
||||
width: 36rem;
|
||||
height: 20rem;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 0;
|
||||
border: 0 solid #ffffff;
|
||||
|
||||
background-color: #5f5f5f;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 120%;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1.0);
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"][label]:before {
|
||||
content: attr(label);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
height: 5rem;
|
||||
|
||||
line-height: 5rem;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"]:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
|
||||
transition-duration: 0.06s;
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].focused {
|
||||
transform: none;
|
||||
border-width: 1.2rem;
|
||||
background-size: 100%;
|
||||
transition-duration: 0.24s;
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].pressed {
|
||||
transform: scale(0.945, 0.9);
|
||||
}
|
||||
|
||||
smart-button[type="banner-button"].pressed:after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* banner button
|
||||
**/
|
||||
|
||||
/**
|
||||
* circle text button
|
||||
*/
|
||||
smart-button[type="circle-text"] {
|
||||
width: 21rem;
|
||||
height: 21rem;
|
||||
color: white;
|
||||
background-color: #5f6368;
|
||||
line-height: 21rem;
|
||||
padding: 2.5rem 2rem 1.5rem 2rem;
|
||||
text-align: center;
|
||||
font-size: 3.2rem;
|
||||
font-weight: bolder;
|
||||
overflow: hidden;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].focused {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
background-color: white;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].pressed {
|
||||
transform: scale(1);
|
||||
color: white;
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary {
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary.focused {
|
||||
color: #ffffff;
|
||||
background-color: #00caf2;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].primary.pressed {
|
||||
background-color: #0097b5;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger {
|
||||
background-color: #e63d2f;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger.focused {
|
||||
color: #ffffff;
|
||||
background-color: #e63d2f;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="circle-text"].danger.pressed {
|
||||
background-color: #ac2e23;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of circle text button
|
||||
*/
|
||||
|
||||
/**
|
||||
* rounded-corner text button
|
||||
*/
|
||||
smart-button[type="rounded-corner-text"] {
|
||||
-moz-user-select: none;
|
||||
width: auto;
|
||||
height: 8rem;
|
||||
border-radius: 4rem;
|
||||
background-color: #5f6368;
|
||||
padding: 0 3.8rem;
|
||||
line-height: 8rem;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 2.4rem;
|
||||
overflow: hidden;
|
||||
transition-property: transform, color, background-color;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].focused,
|
||||
smart-button[type="rounded-corner-text"]:hover {
|
||||
transform: scale(1.2);
|
||||
color: #2d2d2d;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].pressed {
|
||||
transform: scale(1);
|
||||
/*color: white;*/
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary {
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary:hover,
|
||||
smart-button[type="rounded-corner-text"].primary.focused {
|
||||
color: #ffffff;
|
||||
background-color: #00caf2;
|
||||
}
|
||||
|
||||
smart-button[type="rounded-corner-text"].primary.pressed {
|
||||
background-color: #0097b5;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of rounded-corner text button
|
||||
*/
|
||||
30
gaia/tv_apps/browser/bower_components/smart-icons/.bower.json
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "smart-icons",
|
||||
"version": "0.0.3",
|
||||
"homepage": "https://github.com/smart-components/smart-icons",
|
||||
"description": "Icon font for smart-screen",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"templates",
|
||||
"images",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"Gruntfile.js",
|
||||
"README.md",
|
||||
"package.json"
|
||||
],
|
||||
"_release": "0.0.3",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.0.3",
|
||||
"commit": "0c335ac23f484069b4fafa7b7e5afd9f3c08d66e"
|
||||
},
|
||||
"_source": "git://github.com/smart-components/smart-icons.git",
|
||||
"_target": "~0.0.3",
|
||||
"_originalSource": "smart-components/smart-icons"
|
||||
}
|
||||
21
gaia/tv_apps/browser/bower_components/smart-icons/bower.json
vendored
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "smart-icons",
|
||||
"version": "0.0.3",
|
||||
"homepage": "https://github.com/smart-components/smart-icons",
|
||||
"description": "Icon font for smart-screen",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"templates",
|
||||
"images",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"Gruntfile.js",
|
||||
"README.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
BIN
gaia/tv_apps/browser/bower_components/smart-icons/fonts/smart-icons.ttf
vendored
Executable file
25
gaia/tv_apps/browser/bower_components/smart-icons/smart-icons-embedded.css
vendored
Executable file
25
gaia/tv_apps/browser/bower_components/smart-icons/smart-icons.css
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
/* Generated by grunt-webfont */
|
||||
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "smart-icons";
|
||||
src: url("fonts/smart-icons.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
[data-icon]:before,
|
||||
.ligature-icons {
|
||||
font-family: "smart-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;
|
||||
}
|
||||
88
gaia/tv_apps/browser/build/build.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var utils = require('utils');
|
||||
var preprocessor = require('preprocessor');
|
||||
|
||||
var BrowserAppBuilder = function() {
|
||||
};
|
||||
|
||||
BrowserAppBuilder.prototype.setOptions = function(options) {
|
||||
this.stageDir = utils.getFile(options.STAGE_APP_DIR);
|
||||
this.appDir = utils.getFile(options.APP_DIR);
|
||||
this.distDirPath = options.GAIA_DISTRIBUTION_DIR;
|
||||
};
|
||||
|
||||
BrowserAppBuilder.prototype.initJSON = function() {
|
||||
var defaultJSONpath =
|
||||
utils.joinPath(this.appDir.path, 'build', 'default.json');
|
||||
var defaultJson = utils.getJSON(utils.getFile(defaultJSONpath));
|
||||
var file =
|
||||
utils.getFile(this.stageDir.path, 'js', 'init.json');
|
||||
utils.writeContent(file,
|
||||
utils.getDistributionFileContent('browser', defaultJson, this.distDirPath));
|
||||
};
|
||||
|
||||
BrowserAppBuilder.prototype.initTopsitesJSON = function() {
|
||||
var defaultJSONpath =
|
||||
utils.joinPath(this.appDir.path, 'build', 'topsites.json');
|
||||
var defaultJson = utils.getJSON(utils.getFile(defaultJSONpath));
|
||||
|
||||
defaultJson.topSites.forEach(function(site) {
|
||||
if (site.iconPath) {
|
||||
var file = utils.getFile(this.appDir.path, 'build', site.iconPath);
|
||||
var icon = utils.getFileAsDataURI(file);
|
||||
site.iconUri = icon;
|
||||
delete site.iconPath;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
var file =
|
||||
utils.getFile(this.stageDir.path, 'js', 'inittopsites.json');
|
||||
utils.writeContent(file,
|
||||
utils.getDistributionFileContent('topsites', defaultJson,
|
||||
this.distDirPath));
|
||||
};
|
||||
|
||||
BrowserAppBuilder.prototype.enableFirefoxSync = function(options) {
|
||||
var fileList = {
|
||||
process: [
|
||||
['index.html'],
|
||||
['js', 'awesomescreen.js'],
|
||||
['js', 'bookmarkStore.js'],
|
||||
['js', 'historyStore.js'],
|
||||
['js', 'browser_dialog.js'],
|
||||
['js', 'index.js'],
|
||||
['js', 'settings.js'],
|
||||
['js', 'toolbar.js'],
|
||||
['style', 'settings.css'],
|
||||
['style', 'browser_dialog.css']
|
||||
],
|
||||
remove: [
|
||||
['js', 'sync', 'bookmarks.js'],
|
||||
['js', 'sync', 'db.js'],
|
||||
['js', 'sync', 'ds_helper.js'],
|
||||
['js', 'sync', 'history.js'],
|
||||
['js', 'sync', 'manager_bridge.js'],
|
||||
['js', 'sync', 'settings.js'],
|
||||
['js', 'sync', 'toolbar.js'],
|
||||
['test', 'unit', 'sync', 'manager_bridge_test.js'],
|
||||
['test', 'unit', 'sync', 'settings_test.js'],
|
||||
['test', 'unit', 'sync', 'toolbar_test.js']
|
||||
]
|
||||
};
|
||||
preprocessor.execute(options, 'FIREFOX_SYNC', fileList);
|
||||
};
|
||||
|
||||
BrowserAppBuilder.prototype.execute = function(options) {
|
||||
this.setOptions(options);
|
||||
this.initJSON();
|
||||
this.initTopsitesJSON();
|
||||
this.enableFirefoxSync(options);
|
||||
};
|
||||
|
||||
exports.execute = function(options) {
|
||||
utils.copyToStage(options);
|
||||
(new BrowserAppBuilder()).execute(options);
|
||||
};
|
||||
39
gaia/tv_apps/browser/build/default.json
Executable file
BIN
gaia/tv_apps/browser/build/firefox.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
gaia/tv_apps/browser/build/mozilla.png
Executable file
|
After Width: | Height: | Size: 407 B |
14
gaia/tv_apps/browser/build/topsites.json
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"topSites": [
|
||||
{
|
||||
"title": "Mozilla",
|
||||
"uri": "http://mozilla.org",
|
||||
"iconPath": "mozilla.png"
|
||||
},
|
||||
{
|
||||
"title": "Firefox OS",
|
||||
"uri": "http://mozilla.org/firefoxos",
|
||||
"iconPath": "firefox.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
58
gaia/tv_apps/browser/elements/fte.css
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
.fte-container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
color: #fff;
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s linear;
|
||||
}
|
||||
|
||||
.fte-container.finish {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fte-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fte-logo {
|
||||
margin: 7.5rem 0 1.5rem 0;
|
||||
}
|
||||
|
||||
.fte-greeting {
|
||||
margin: 1.5rem 0 8rem 0;
|
||||
line-height: 7rem;
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
font-size: 7rem;
|
||||
}
|
||||
|
||||
.fte-description {
|
||||
width: 110rem;
|
||||
margin: 8rem auto 3.5rem auto;
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
font-size: 3.8rem;
|
||||
}
|
||||
|
||||
.fte-button-container {
|
||||
position: absolute;
|
||||
bottom: 10.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fte-button-container > smart-button {
|
||||
width: 23.7rem;
|
||||
font-size: 2.4rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fte-button-container > smart-button:first-child {
|
||||
margin-right: 1.8rem;
|
||||
}
|
||||
14
gaia/tv_apps/browser/elements/fte.html
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div id="fte-container" class="fte-container">
|
||||
<div class="fte-page">
|
||||
<img class="fte-logo" src="../style/images/firefox_logo.png"></img>
|
||||
<h1 class="fte-greeting" data-l10n-id="fte-welcome"></h1>
|
||||
<p class="fte-description" data-l10n-id="fte-description"></p>
|
||||
<img class="fte-diagram" src="../style/images/account_image.png"></img>
|
||||
<div class="fte-button-container">
|
||||
<smart-button id="fte-skip" class="fte-button" type="rounded-corner-text" data-l10n-id="skip" data-behavior="skip"></smart-button>
|
||||
<smart-button id="fte-sign-in" class="fte-button fte-finish primary" type="rounded-corner-text" class="primary" data-l10n-id="login" data-behavior="finish"></smart-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
BIN
gaia/tv_apps/browser/img/key_active.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
gaia/tv_apps/browser/img/scroll_down.cur
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
gaia/tv_apps/browser/img/scroll_left.cur
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
gaia/tv_apps/browser/img/scroll_right.cur
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
gaia/tv_apps/browser/img/scroll_up.cur
Executable file
|
After Width: | Height: | Size: 17 KiB |
828
gaia/tv_apps/browser/index.html
Executable file
|
|
@ -0,0 +1,828 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US" dir="ltr">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>web browser</title>
|
||||
|
||||
<!-- building blocks -->
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-button/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/smart-icons/smart-icons.css">
|
||||
|
||||
<!-- Specific style -->
|
||||
<link rel="stylesheet" type="text/css" href="style/components.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/awesomescreen.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/authentication_dialog.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/browser_dialog.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/toolbar.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/tooltip.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/search_result.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/settings.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/smartList.css">
|
||||
|
||||
<!-- Resources -->
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_336.png">
|
||||
|
||||
<!-- Localization -->
|
||||
<script defer="" src="/shared/js/l10n.js"></script>
|
||||
|
||||
<!-- L10n properties -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<!--IFDEF_FIREFOX_SYNC
|
||||
<link rel="localization" href="shared/locales/firefox_sync/firefox_sync.{locale}.properties">
|
||||
ENDIF_FIREFOX_SYNC-->
|
||||
<link rel="localization" href="/locales/browser.{locale}.properties">
|
||||
|
||||
<!-- building blocks -->
|
||||
<script defer src="bower_components/evt/index.js"></script>
|
||||
<script defer src="bower_components/smart-button/script.js"></script>
|
||||
|
||||
<!-- Shared code -->
|
||||
<script defer src="shared/js/url_helper.js"></script>
|
||||
<script defer src="shared/js/utilities.js"></script>
|
||||
<script defer src="shared/js/smart-screen/connection_manager.js"></script>
|
||||
<script defer src="shared/js/lazy_loader.js"></script>
|
||||
<script defer src="shared/js/smart-screen/simple_key_navigation.js"></script>
|
||||
<script defer src="shared/js/smart-screen/f_t_e_wizard.js"></script>
|
||||
|
||||
<!-- Specific code -->
|
||||
<script defer src="js/resource_loader.js"></script>
|
||||
<script defer src="js/browser_db.js"></script>
|
||||
<!--IFDEF_FIREFOX_SYNC
|
||||
<script defer src="js/sync/db.js"></script>
|
||||
ENDIF_FIREFOX_SYNC-->
|
||||
<script defer src="js/index.js"></script>
|
||||
<script defer src="js/awesomescreen.js"></script>
|
||||
<script defer src="js/search_util.js"></script>
|
||||
<script defer src="js/authentication_dialog.js"></script>
|
||||
<script defer src="js/browser_dialog.js"></script>
|
||||
<script defer src="js/toolbar.js"></script>
|
||||
<script defer src="js/tooltip.js"></script>
|
||||
<script defer src="js/search_result.js"></script>
|
||||
<script defer src="js/settings.js"></script>
|
||||
<script defer src="js/connection_handler.js"></script>
|
||||
<script defer src="js/smartList.js"></script>
|
||||
<script defer src="js/bookmarkStore.js"></script>
|
||||
<script defer src="js/historyStore.js"></script>
|
||||
<script defer src="js/fte.js"></script>
|
||||
|
||||
<!-- IAC Handler (put here because it starts sending event immediately) -->
|
||||
<script defer src="shared/js/iac_handler.js"></script>
|
||||
|
||||
<!-- Lazy loaded code -->
|
||||
<!-- <script src="shared/js/settings_listener.js"><script> -->
|
||||
<!-- <script src="shared/js/sync/errors.js"></script> -->
|
||||
<!-- <script src="js/sync/manager_bridge.js"></script> -->
|
||||
<!-- <script src="js/sync/settings.js"></script> -->
|
||||
<!-- <script src="js/sync/toolbar.js"></script> -->
|
||||
<!-- <script src="js/sync/ds_helper.js"></script> -->
|
||||
<!-- <script src="js/sync/bookmarks.js"></script> -->
|
||||
<!-- <script src="js/sync/history.js"></script> -->
|
||||
|
||||
<!-- Lazy loaded Templates -->
|
||||
<!-- <link rel="import" href="elements/fte.html"> -->
|
||||
<!-- <link rel="stylesheet" type="text/css" href="elements/fte.css"> -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="page_screen start_page" role="application">
|
||||
<!-- mask for menu -->
|
||||
<div id="fade-base"></div>
|
||||
|
||||
<!-- side -->
|
||||
<div id="side-block" class="side-window">
|
||||
<!-- put widget here -->
|
||||
</div>
|
||||
|
||||
<!-- main -->
|
||||
<div id="main-block" data-mode="cursor" class="main-window">
|
||||
<!-- toolbar -->
|
||||
<ul id="toolbar-panel" data-pvt="false">
|
||||
<div id="private-bar"></div>
|
||||
<li id="sidebar-button-block" data-tips="WB_LT_TIPS_CHANGE_SCREEN" data-fade="false">
|
||||
<div id="sidebar-button" data-sidebar="true">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="sidebar-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid slice">
|
||||
<g id="sidebar-close-svg" visibility="hidden">
|
||||
<g>
|
||||
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M36.899,15.805H18.214c-1.968,0-3.716,2.093-3.716,4.061v14.997
|
||||
c0,1.968,1.747,3.332,3.716,3.332h18.686c1.968,0,3.502-1.364,3.502-3.332V19.866C40.401,17.898,38.867,15.805,36.899,15.805z
|
||||
M37.578,32.739c0,1.583-1.234,2.68-2.817,2.68H20.265c-1.583,0-2.989-1.097-2.989-2.68V21.849c0-1.583,1.406-3.267,2.989-3.267
|
||||
h14.497c1.583,0,2.817,1.684,2.817,3.267V32.739z"></path>
|
||||
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M16.589,22.805h22.4v2.1h-22.4V22.805z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="sidebar-open-svg" visibility="hidden">
|
||||
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M36.45,15.805H17.764c-1.968,0-3.716,2.093-3.716,4.061v14.997
|
||||
c0,1.968,1.747,3.332,3.716,3.332H36.45c1.968,0,3.502-1.364,3.502-3.332V19.866C39.952,17.898,38.418,15.805,36.45,15.805z
|
||||
M37.129,21.849v0.955H23.839v-4.222h10.472C35.895,18.583,37.129,20.266,37.129,21.849z M34.312,35.419H23.839V24.905h13.289
|
||||
v7.834C37.129,34.322,35.895,35.419,34.312,35.419z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li id="back-button-block" data-tips="WB_LT_TIPS_HISTORY_BACK">
|
||||
<div id="back-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="back-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="back-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.325,24.619H21.352l6.701-6.701
|
||||
c-0.38-1.648-0.861-3.185-1.428-4.584L15.46,24.5c-1.385,1.384-1.385,3.628,0,5.012l11.153,11.154
|
||||
c0.567-1.396,1.048-2.928,1.431-4.57L21.349,29.4h17.976C39.662,27.764,39.661,26.256,39.325,24.619z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="forward-button-block" class="disable" data-tips="WB_LT_TIPS_HISTORY_FORWARD">
|
||||
<div id="forward-button-border">
|
||||
<div id="forward-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="forward-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="forward-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.54,24.5L27.375,13.334
|
||||
c-0.567,1.399-1.048,2.936-1.429,4.584l6.702,6.701H14.675c-0.338,1.637-0.338,3.145,0,4.781h17.976l-6.695,6.695
|
||||
c0.383,1.643,0.863,3.174,1.431,4.57L38.54,29.512C39.925,28.128,39.925,25.883,38.54,24.5z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li id="space-block">
|
||||
<div></div>
|
||||
</li>
|
||||
<li id="addressbar-block">
|
||||
<form id="address-form">
|
||||
<input id="private-icon" value="" type="image">
|
||||
<input id="ssl-indicator" value="" type="image">
|
||||
<input id="url-input" dir="auto" type="text">
|
||||
<div class="input-line"></div>
|
||||
<div id="address-button" data-mode="load" data-tips="WB_LT_TIPS_RELOAD">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="address-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<g id="address-reload-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="address-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.701,24.33c-0.953-7.119-7.035-12.616-14.416-12.616
|
||||
c-8.041,0-14.56,6.52-14.56,14.561c0,8.042,6.519,14.56,14.56,14.56c4.022,0,7.663-1.63,10.298-4.266l-2.231-2.231
|
||||
c-2.064,2.065-4.916,3.343-8.067,3.343c-6.299,0-11.405-5.107-11.405-11.405c0-6.299,5.106-11.405,11.405-11.405
|
||||
c5.622,0,10.282,4.071,11.222,9.423c-1.445,0.126-2.765,0.437-3.866,0.883l4.343,4.343c0.539,0.539,1.413,0.539,1.951,0
|
||||
l4.339-4.338C42.251,24.763,41.03,24.472,39.701,24.33z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="address-stop-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="address-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" points="37.553,19.225 34.776,16.448 26.983,24.241
|
||||
19.207,16.465 16.43,19.242 24.206,27.018 16.448,34.776 19.225,37.553 26.983,29.795 34.758,37.57 37.536,34.793 29.76,27.018
|
||||
"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li id="searchbar-block">
|
||||
<form id="search-form">
|
||||
<input id="search-favicon" value="" type="image">
|
||||
<input id="search-input" data-tips="WB_LT_TIPS_SEARCH_BAR" dir="auto" type="text">
|
||||
<div class="input-line"></div>
|
||||
<div id="search-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="search-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="search-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.774,36.538l-5.462-5.461
|
||||
c1.128-1.731,1.789-3.793,1.789-6.012c0-6.096-4.943-11.036-11.037-11.036c-6.096,0-11.035,4.94-11.035,11.036
|
||||
c0,6.093,4.939,11.036,11.035,11.036c2.218,0,4.278-0.661,6.008-1.787l5.462,5.462c0.455,0.454,1.547,0.098,2.442-0.796
|
||||
C39.87,38.085,40.227,36.992,39.774,36.538z M25.065,32.271c-3.98,0-7.206-3.227-7.206-7.207c0-3.981,3.227-7.208,7.206-7.208
|
||||
c3.981,0,7.207,3.227,7.207,7.208C32.272,29.045,29.046,32.271,25.065,32.271z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="search-clear-button"></div>
|
||||
</form>
|
||||
<button id="search-close-button" data-l10n-id="LT_NETWORK_STATUS_BTN_CLOSE">close</button>
|
||||
</li>
|
||||
|
||||
<li id="iconbutton-block">
|
||||
<ul>
|
||||
<li id="bookmark-button-block" data-tips="WB_LT_TIPS_BOOKMARK_THIS_PAGE">
|
||||
<div id="bookmark-button" data-isbookmark="false">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="bookmark-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<g id="bookmark-off-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="bookmark-button-path" fill="#1D1D1B" d="M43.116,22.382l-11.238-1.477L27,10.673l-4.878,10.231l-11.238,1.477l8.224,7.801L17.04,41.327l9.96-5.41
|
||||
l9.96,5.41l-2.068-11.145L43.116,22.382z M27,32.824l-6.404,3.479l1.33-7.165l-5.287-5.016l7.225-0.949L27,16.594l3.136,6.579
|
||||
l7.226,0.949l-5.287,5.016l1.33,7.165L27,32.824z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="bookmark-on-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="bookmark-button-path" fill="#00caf2" points="43.116,22.382 31.878,20.904 27,10.673 22.122,20.904 10.884,22.382 19.107,30.183 17.04,41.327
|
||||
27,35.917 36.96,41.327 34.893,30.183 "></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="show-bookmarks-button-block" data-tips="WB_LT_TIPS_SHOW_YOUR_BOOKMARK">
|
||||
<div id="show-bookmarks-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="show-bookmarks-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="show-bookmarks-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.551,18.436h-4.794l-5.571-5.406l-5.581,5.406h-5.156
|
||||
c-1.205,0-2.181,0.977-2.181,2.181v18.172c0,1.204,0.976,2.181,2.181,2.181h21.102c1.204,0,2.181-0.977,2.181-2.181V20.617
|
||||
C40.731,19.413,39.755,18.436,38.551,18.436z M21.83,38.06h-4.363v-4.363h4.363V38.06z M21.833,31.521h-4.361v-4.361h4.361
|
||||
V31.521z M21.833,24.978h-4.361v-4.361h4.361V24.978z M23.991,27.159h14.56v4.361h-14.56V27.159z M38.552,38.06H23.999v-4.363
|
||||
h14.553V38.06z M38.542,24.968l-14.538,0.007v-4.356h14.548L38.542,24.968z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="home-button-block" data-tips="WB_LT_TIPS_HOMEPAGE">
|
||||
<div id="home-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="home-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="home-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M27.557,19.535l-9.028,8.469
|
||||
c-0.001,0.026-0.007,0.05-0.007,0.076v10.34c0,1.119,0.935,2.071,2.088,2.071h4.028c-0.188-0.238-0.305-0.534-0.305-0.86v-7.936
|
||||
c0-0.769,0.623-1.393,1.392-1.393h3.756c0.769,0,1.392,0.624,1.392,1.393v7.936c0,0.326-0.117,0.623-0.305,0.86h4.044
|
||||
c1.153,0,2.088-0.952,2.088-2.071v-10.34c0-0.014-0.004-0.027-0.004-0.041L27.557,19.535z M40.929,25.98L29.117,14.352
|
||||
c-0.815-0.791-2.137-0.791-2.953,0L14.352,25.98c-0.418,0.406-0.618,0.94-0.608,1.472c0.013,0,0.024-0.003,0.037-0.003h2.553
|
||||
l9.863-9.18l-0.035-0.032l0.651-0.541l0.895-0.833l1.373,1.243l-0.033,0.031l9.832,9.312h2.658
|
||||
C41.547,26.919,41.347,26.385,40.929,25.98z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="zoom-button-block" data-tips="WB_LT_TIPS_ZOOM_IN" data-colorbar="red">
|
||||
<div id="zoom-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="zoom-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="zoom-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.982,36.892l-6.505-6.505
|
||||
c1.148-1.698,1.818-3.746,1.818-5.95c0-5.876-4.764-10.64-10.64-10.64c-5.876,0-10.639,4.764-10.639,10.64
|
||||
c0,5.876,4.763,10.64,10.639,10.64c2.074,0,4.003-0.603,5.639-1.629l6.063,6.063c0.005,0.203,0.06,0.379,0.184,0.502
|
||||
c0.438,0.438,1.492,0.095,2.353-0.767c0.755-0.755,1.112-1.657,0.895-2.164L39.982,36.892z M24.656,32.544
|
||||
c-4.478,0-8.107-3.63-8.107-8.107c0-4.477,3.63-8.108,8.107-8.108c4.477,0,8.108,3.631,8.108,8.108
|
||||
C32.765,28.914,29.134,32.544,24.656,32.544z M25.766,19.229h-2.09v4.21h-4.212v2.089h4.212v4.192h2.09v-4.192h4.189V23.44
|
||||
h-4.189V19.229z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="tabs-button-block" data-tips="WB_LT_TIPS_TABS" data-tabs="tab01" data-colorbar="yellow">
|
||||
<div id="tabs-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="tabs-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<g id="tabs-00-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.456,24.899c-1.332,0-1.962,1.188-1.962,4.537
|
||||
c0,3.385,0.63,4.573,1.962,4.573c1.333,0,1.963-1.152,1.963-4.573C31.419,26.033,30.789,24.899,29.456,24.899z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M29.456,35.757c-2.845,0-4.339-2.27-4.339-6.32c0-4.033,1.495-6.284,4.339-6.284c2.863,0,4.339,2.251,4.339,6.284
|
||||
C33.795,33.487,32.319,35.757,29.456,35.757z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="tabs-01-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M31.806,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z"></path>
|
||||
</g>
|
||||
<g id="tabs-02-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M33.057,35.486h-7.544v-1.692c4.141-4.214,5.149-5.348,5.149-7.059c0-1.116-0.702-1.765-1.747-1.765
|
||||
c-0.954,0-1.584,0.396-2.323,1.279l-1.422-1.117c0.972-1.26,2.25-1.98,3.925-1.98c2.503,0,3.943,1.513,3.943,3.403
|
||||
c0,2.053-1.17,3.547-4.916,7.112h5.186L33.057,35.486z"></path>
|
||||
</g>
|
||||
<g id="tabs-03-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M28.889,35.757c-1.656,0-2.989-0.612-3.925-1.747l1.314-1.242c0.738,0.828,1.531,1.188,2.485,1.188
|
||||
c1.296,0,2.143-0.774,2.143-2.035c0-1.44-0.81-1.944-2.179-1.944h-0.954l0.27-1.639h0.648c1.135,0,1.945-0.647,1.945-1.836
|
||||
c0-0.99-0.702-1.585-1.783-1.585c-0.936,0-1.674,0.343-2.431,1.062l-1.17-1.278c1.081-1.026,2.341-1.549,3.799-1.549
|
||||
c2.503,0,3.835,1.404,3.835,3.079c0,1.513-0.936,2.467-2.358,2.845c1.512,0.145,2.755,1.08,2.755,2.953
|
||||
C33.282,34.1,31.662,35.757,28.889,35.757z"></path>
|
||||
</g>
|
||||
<g id="tabs-04-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M33.967,32.714h-1.459v2.772h-2.25v-2.772h-5.149v-1.585l3.528-7.994l1.963,0.738l-3.097,7.076h2.772l0.234-3.043h1.998v3.043
|
||||
h1.459V32.714z"></path>
|
||||
</g>
|
||||
<g id="tabs-05-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M28.943,35.757c-1.692,0-2.881-0.685-3.817-1.711l1.332-1.242c0.648,0.756,1.423,1.17,2.431,1.17c1.351,0,2.197-0.864,2.197-2.431
|
||||
c0-1.674-0.828-2.25-1.944-2.25c-0.559,0-1.009,0.126-1.566,0.414h-1.639v-6.338h7.021l-0.27,1.71h-4.537v3.007
|
||||
c0.611-0.306,1.242-0.45,1.836-0.45c2.017,0,3.476,1.44,3.476,3.907C33.463,34.028,31.68,35.757,28.943,35.757z"></path>
|
||||
</g>
|
||||
<g id="tabs-06-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.397-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.861,29.095c-0.828,0-1.584,0.485-2.143,1.242
|
||||
c0.036,2.593,0.595,3.709,1.98,3.709c1.225,0,1.818-1.062,1.818-2.646C31.518,29.671,30.888,29.095,29.861,29.095z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M29.699,35.757c-3.097,0-4.375-2.396-4.375-5.87c0-4.051,1.891-6.734,4.97-6.734c1.062,0,1.944,0.307,2.646,0.774l-0.864,1.423
|
||||
c-0.522-0.307-1.099-0.486-1.746-0.486c-1.531,0-2.503,1.458-2.629,3.889c0.773-0.936,1.692-1.332,2.719-1.332
|
||||
c1.891,0,3.421,1.368,3.421,3.943C33.841,33.902,32.148,35.757,29.699,35.757z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="tabs-07-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M33.165,24.989l-4.483,10.731l-2.106-0.685l4.213-9.885h-5.113v-1.782h7.49V24.989z"></path>
|
||||
</g>
|
||||
<g id="tabs-08-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.366,29.995l-0.558-0.198
|
||||
c-0.972,0.521-1.458,1.296-1.458,2.395c0,1.152,0.829,1.854,2.125,1.854c1.351,0,2.107-0.774,2.107-1.818
|
||||
C31.581,31.093,30.986,30.589,29.366,29.995z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.564,28.284l0.342,0.126c0.954-0.594,1.296-1.188,1.296-2.053
|
||||
c0-0.954-0.594-1.584-1.729-1.584c-1.044,0-1.729,0.54-1.729,1.603C27.745,27.402,28.394,27.852,29.564,28.284z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M29.42,35.757c-2.719,0-4.394-1.531-4.394-3.493c0-1.477,0.864-2.449,2.305-3.062c-1.296-0.702-1.818-1.674-1.818-2.862
|
||||
c0-1.98,1.801-3.188,3.961-3.188c2.143,0,3.961,1.081,3.961,3.098c0,1.116-0.63,2.034-1.999,2.736
|
||||
c1.656,0.738,2.485,1.801,2.485,3.241C33.921,34.208,32.175,35.757,29.42,35.757z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="tabs-09-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.411,24.863c-1.17,0-1.908,0.811-1.908,2.358
|
||||
c0,1.656,0.756,2.305,1.729,2.305c0.828,0,1.53-0.45,2.07-1.242C31.337,25.781,30.761,24.863,29.411,24.863z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M26.602,35.937l-0.486-1.639c3.025-0.792,4.753-1.963,5.078-4.357c-0.576,0.738-1.422,1.261-2.593,1.261
|
||||
c-1.873,0-3.403-1.494-3.403-3.943c0-2.539,1.873-4.105,4.213-4.105c2.809,0,4.25,1.909,4.25,4.699
|
||||
C33.66,32.84,31.463,34.784,26.602,35.937z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="tabs-10-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M33.506,24.899c-1.332,0-1.962,1.188-1.962,4.537
|
||||
c0,3.385,0.63,4.573,1.962,4.573c1.333,0,1.963-1.152,1.963-4.573C35.469,26.033,34.838,24.899,33.506,24.899z"></path>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M26.648,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z M33.506,35.757
|
||||
c-2.845,0-4.339-2.27-4.339-6.32c0-4.033,1.495-6.284,4.339-6.284c2.863,0,4.339,2.251,4.339,6.284
|
||||
C37.845,33.487,36.369,35.757,33.506,35.757z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="tabs-11-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M27.755,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z M35.855,35.486h-2.269v-9.795l-2.701,1.639
|
||||
l-1.008-1.549l3.943-2.412h2.035V35.486z"></path>
|
||||
</g>
|
||||
<g id="tabs-12-svg" visibility="hidden">
|
||||
<g>
|
||||
<g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
|
||||
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
|
||||
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
|
||||
M27.243,35.486h-2.27v-9.795l-2.7,1.639l-1.009-1.549l3.943-2.412h2.035V35.486z M37.107,35.486h-7.544v-1.692
|
||||
c4.141-4.214,5.149-5.348,5.149-7.059c0-1.116-0.702-1.765-1.747-1.765c-0.954,0-1.584,0.396-2.322,1.279l-1.423-1.117
|
||||
c0.973-1.26,2.251-1.98,3.926-1.98c2.502,0,3.942,1.513,3.942,3.403c0,2.053-1.17,3.547-4.915,7.112h5.186L37.107,35.486z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="new-tab-button-block" data-tips="WB_LT_TIPS_OPEN_A_NEW_TAB" data-colorbar="blue">
|
||||
<div id="new-tab-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="new-tab-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="new-tab-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M33.59,16.501c-0.176-1.671-1.595-2.786-3.341-2.786H16.746
|
||||
c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.738,1.162,3.154,2.83,3.339V20.219c0-1.864,1.534-3.718,3.398-3.718H33.59z
|
||||
M36.814,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297c0,1.968,1.748,3.332,3.716,3.332h14.485
|
||||
c1.968,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.814,18.595z M35.394,30.061
|
||||
c0,0.499-0.405,1.127-0.904,1.127h-3.981v4.006c0,0.499-0.602,0.904-1.102,0.904h-0.524c-0.5,0-1.189-0.405-1.189-0.904v-4.006
|
||||
h-4c-0.5,0-0.904-0.628-0.904-1.127v-0.452c0-0.5,0.405-1.232,0.904-1.232h4v-3.982c0-0.5,0.689-0.904,1.189-0.904h0.524
|
||||
c0.5,0,1.102,0.404,1.102,0.904v3.982h3.981c0.5,0,0.904,0.732,0.904,1.232V30.061z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li id="menu-button-block" data-tips="WB_LT_TIPS_MENU">
|
||||
<div id="menu-button">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="menu-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="menu-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M36.783,16.15H17.217c-0.773,0-1.4,0.626-1.4,1.399v1.422
|
||||
c0,0.773,0.627,1.4,1.4,1.4h19.566c0.773,0,1.4-0.627,1.4-1.4V17.55C38.183,16.776,37.557,16.15,36.783,16.15z M36.783,24.594
|
||||
H17.217c-0.773,0-1.4,0.627-1.4,1.4v1.378c0,0.773,0.627,1.399,1.4,1.399h19.566c0.773,0,1.4-0.626,1.4-1.399v-1.378
|
||||
C38.183,25.221,37.557,24.594,36.783,24.594z M36.783,33.672H17.217c-0.773,0-1.4,0.627-1.4,1.4v1.378
|
||||
c0,0.773,0.627,1.399,1.4,1.399h19.566c0.773,0,1.4-0.626,1.4-1.399v-1.378C38.183,34.299,37.557,33.672,36.783,33.672z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<ul id="menu-block">
|
||||
<div>
|
||||
<li id="history-block"><a id="history-tab" href="#" data-l10n-id="LT_SE_HISTORY"></a></li>
|
||||
<li id="pocket-block"><a id="pocket-tab" href="#" data-l10n-id="pocket"></a></li>
|
||||
<li id="private-window-block"><a id="private-window-tab" href="#" data-l10n-id="WB_LT_TIPS_NEW_PRIVATE_WINDOW"></a></li>
|
||||
<!--IFDEF_FIREFOX_SYNC
|
||||
<li id="fxsync-block"><a id="fxsync-tab" href="#" data-l10n-id="fxsync-sign-in-to-sync"></a></li>
|
||||
ENDIF_FIREFOX_SYNC-->
|
||||
<li id="settings-block"><a id="settings-tab" href="#" data-l10n-id="LT_APPS_SETTINGS"></a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- bookmark animation -->
|
||||
<div id="bookmark-button-anime">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="bookmark-button-anime-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 54 54" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<polygon class="bookmark-button-path" fill="#00CAF2" points="43.116,22.382 31.878,20.904 27,10.673 22.122,20.904 10.884,22.382 19.107,30.183 17.04,41.327
|
||||
27,35.917 36.96,41.327 34.893,30.183"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="show-bookmarks-button-anime">
|
||||
<!-- XXX: Replace the SVG to icon font -->
|
||||
<svg id="show-bookmarks-button-anime-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 54 54" x="0" y="0" preserveAspectRatio="xMidyMid meet">
|
||||
<path class="show-bookmarks-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.551,18.436h-4.794l-5.571-5.406l-5.581,5.406h-5.156
|
||||
c-1.205,0-2.181,0.977-2.181,2.181v18.172c0,1.204,0.976,2.181,2.181,2.181h21.102c1.204,0,2.181-0.977,2.181-2.181V20.617
|
||||
C40.731,19.413,39.755,18.436,38.551,18.436z M21.83,38.06h-4.363v-4.363h4.363V38.06z M21.833,31.521h-4.361v-4.361h4.361
|
||||
V31.521z M21.833,24.978h-4.361v-4.361h4.361V24.978z M23.991,27.159h14.56v4.361h-14.56V27.159z M38.552,38.06H23.999v-4.363
|
||||
h14.553V38.06z M38.542,24.968l-14.538,0.007v-4.356h14.548L38.542,24.968z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- web -->
|
||||
<div id="web-block">
|
||||
<!--
|
||||
<iframe mozbrowser remote mozasyncpanzoom mozallowfullscreen id="browser_iframe" scrolling="yes"></iframe>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<!-- scroll up down -->
|
||||
<div id="scroll-u"></div>
|
||||
<div id="scroll-d"></div>
|
||||
|
||||
<div id="awesomescreen">
|
||||
<!-- awesomescreen tabview -->
|
||||
<section id="tab-view">
|
||||
<div id="tabview-panels">
|
||||
<ul id="tab-site-list" role="listbox">
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="top-sites">
|
||||
<div id="top-default-panels">
|
||||
<ul id="top-default-list">
|
||||
<li id="default-top-list-0" class="top-default-item"></li>
|
||||
<li id="default-top-list-1" class="top-default-item"></li>
|
||||
<li id="default-top-list-2" class="top-default-item"></li>
|
||||
<li id="default-top-list-3" class="top-default-item"></li>
|
||||
<li id="default-top-list-4" class="top-default-item"></li>
|
||||
<li id="default-top-list-5" class="top-default-item"></li>
|
||||
<li id="default-top-list-6" class="top-default-item"></li>
|
||||
<li id="default-top-list-7" class="top-default-item"></li>
|
||||
<li id="default-top-list-8" class="top-default-item"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="top-panels">
|
||||
<ul id="top-site-list" role="listbox"></ul>
|
||||
</div>
|
||||
</section>
|
||||
<!-- awesomescreen bookamrk,history-->
|
||||
<section id="bookmark" tabindex="0"></section>
|
||||
<section id="history" tabindex="0"></section>
|
||||
<!-- private window -->
|
||||
<div id="private-browsing-block">
|
||||
<div id="private-browsing-panel">
|
||||
<!-- image logo -->
|
||||
<div id="private-browsing-img"></div>
|
||||
|
||||
<!-- title -->
|
||||
<h1 id="private-browsing-title" data-l10n-id="WB_LT_SETTINGS_PRIVATE_BROWSING"></h1>
|
||||
<!-- sum title -->
|
||||
<h2 id="private-browsing-subtitle" data-l10n-id="WB_LT_PRIVATE_BROWSING_1"></h2>
|
||||
|
||||
<!-- info -->
|
||||
<p class="private-browsing-text-block private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_2"></p>
|
||||
<p class="private-browsing-text-block private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_3"></p>
|
||||
|
||||
<!-- more info -->
|
||||
<div id="private-browsing-more-info">
|
||||
<p class="private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_4"></p>
|
||||
<a id="private-browsing-more-info-link" class="private-browsing-text" target="_blank" data-l10n-id="WB_LT_LEARN_MORE"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- awesomescreen alert dialog -->
|
||||
<div id="dialog-area">
|
||||
<div id="bmd-dialog">
|
||||
<div id="message-area">
|
||||
<div id="message-title" class="title-area"></div>
|
||||
<div id="bmtitle-area">
|
||||
<input id="input-area" value="" size="30" type="text" required pattern="\s*\S.*">
|
||||
<div class="input-line"></div>
|
||||
<div id="circle-img"></div>
|
||||
</div>
|
||||
<div id="check-area">
|
||||
<div id="cbox-area">
|
||||
<div id="check-box">
|
||||
<div id="check-box-background"></div>
|
||||
<img id="check-img" src="style/images/check-on.svg">
|
||||
</div>
|
||||
<div id="text-area" data-l10n-id="WB_LT_ALSO_PIN_TO_HOME">Also pin to home</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="button-area">
|
||||
<div id="bt-position">
|
||||
<button class="awesomescreen-primary-button none" id="ok-button" data-l10n-id="LT_WB_OK">OK</button>
|
||||
<button class="awesomescreen-default-button none" id="pinhome-button" data-l10n-id="LT_PIN_TO_HOME">Pin to HOME</button>
|
||||
<button class="awesomescreen-default-button none" id="clhistory-button" data-l10n-id="WB_LT_CLEAR_HISTORY">Clear History</button>
|
||||
<button class="awesomescreen-default-button none" id="remove-button" data-l10n-id="WB_LT_CLEAR_BROWSING_HISTORY">Clear All History</button>
|
||||
<button class="awesomescreen-default-button none" id="remove-bookmark-button" data-l10n-id="WB_LT_REMOVE">Remove</button>
|
||||
<button class="awesomescreen-default-button none" id="edit-button" data-l10n-id="LT_CL_EDIT">Edit</button>
|
||||
<button class="awesomescreen-default-button none" id="sethome-button" data-l10n-id="WB_LT_SET_AS_HOMEPAGE">Set as Homepage</button>
|
||||
<button class="awesomescreen-danger-button none" id="remove-topsite-button" data-l10n-id="WB_LT_REMOVE_FROM_TOP_SITES">Remove from Top Sites</button>
|
||||
<button class="awesomescreen-primary-button none" id="rename-confirm-button" data-l10n-id="LT_WB_OK">OK</button>
|
||||
<button class="awesomescreen-danger-button none" id="remove-confirm-button" data-l10n-id="WB_LT_REMOVE">Remove</button>
|
||||
<button class="awesomescreen-danger-button none" id="clear-button" data-l10n-id="WB_LT_CLEAR">Clear</button>
|
||||
<button class="awesomescreen-default-button none" id="cancel-button" data-l10n-id="LT_CANCEL">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialog-banner-message"></div>
|
||||
<div id="pointer-img"><img src="style/images/img_cursor_arrow2x.png" width="64" height="64"></div>
|
||||
|
||||
<!-- awesomescreen loading -->
|
||||
<div id="awesome-loading-icon">
|
||||
<div class="awesomecircle awesomecircle-1"></div>
|
||||
<div class="awesomecircle awesomecircle-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- authentication -->
|
||||
<section id="http-authentication-dialog" role="region" class="hidden skin-organic">
|
||||
<div id="http-authentication-dialog-wrap">
|
||||
<div id="http-authentication-header">
|
||||
<div id="http-authentication-key"></div>
|
||||
<div id="http-authentication-message"></div>
|
||||
</div>
|
||||
|
||||
<table id="http-authentication-body">
|
||||
<tbody><tr>
|
||||
<td><div class="http-authentication-title" data-l10n-id="LT_FILE_SHARING_DEVICE_USER">User</div></td>
|
||||
<td>
|
||||
<form class="http-authentication-input" id="http-authentication-username">
|
||||
<input class="http-authentication-input-area" id="http-authentication-username-area" dir="auto" type="text">
|
||||
<div class="input-line"></div>
|
||||
<div id="http-authentication-username-clear" class="special-pointer"></div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="http-authentication-title" data-l10n-id="LT_FILE_SHARING_DEVICE_PASSWORD">Password</div></td>
|
||||
<td>
|
||||
<form class="http-authentication-input" id="http-authentication-password">
|
||||
<input class="http-authentication-input-area" id="http-authentication-password-area" type="password">
|
||||
<div class="input-line"></div>
|
||||
<div id="http-authentication-password-clear" class="special-pointer"></div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<div id="http-authentication-button">
|
||||
<button class="http-authentication-button" id="http-authentication-ok">
|
||||
<span data-l10n-id="LT_WB_OK">OK</span>
|
||||
</button>
|
||||
<button class="http-authentication-button" id="http-authentication-cancel">
|
||||
<span data-l10n-id="LT_CANCEL">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- dialog -->
|
||||
<div id="browser-dialog-base" class="hide">
|
||||
<div id="browser-dialog">
|
||||
<div id="browser-dialog-title">
|
||||
<p id="browser-dialog-title-text"></p>
|
||||
</div>
|
||||
<div id="browser-dialog-msg">
|
||||
<p id="browser-dialog-msg-text"></p>
|
||||
</div>
|
||||
<form id="browser-dialog-input">
|
||||
<input id="browser-dialog-input-area">
|
||||
<div class="input-line"></div>
|
||||
<div id="browser-dialog-input-clear"></div>
|
||||
</form>
|
||||
<div id="browser-dialog-margin"></div>
|
||||
<div id="browser-dialog-button">
|
||||
<button class="browser-dialog-button" id="browser-dialog-button2"></button>
|
||||
<button class="browser-dialog-button" id="browser-dialog-button1"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- settings -->
|
||||
<div role="region" id="settings-list" class="skin-organic">
|
||||
<div id="settings-list-area">
|
||||
<div id="settings-header">
|
||||
<p id="settings-header-title" data-l10n-id="WB_LT_BROWSER_SETTINGS">Browser Settings</p>
|
||||
</div>
|
||||
<div id="settings-list-item">
|
||||
<div id="settings-homepage">
|
||||
<p id="settings-homepage-title" data-l10n-id="WB_LT_HOMEPAGE">Homepage</p>
|
||||
<p id="settings-homepage-name"></p>
|
||||
</div>
|
||||
<div id="settings-search-engine">
|
||||
<p id="settings-search-engine-title" data-l10n-id="WB_LT_SETTINGS_SEARCH_ENGINE">Search Engine</p>
|
||||
<p id="settings-search-engine-name"></p>
|
||||
<p id="settings-search-engine-icon"></p>
|
||||
</div>
|
||||
<div id="settings-clear-history">
|
||||
<p id="settings-clear-history-title" data-l10n-id="WB_LT_CLEAR_BROWSING_HISTORY">Clear All History</p>
|
||||
</div>
|
||||
<div id="settings-clear-cookie">
|
||||
<p id="settings-clear-cookie-title" data-l10n-id="WB_LT_CLEAR_STORED_DATA">Clear Cookies and Stored Data</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--IFDEF_FIREFOX_SYNC
|
||||
<div id="fxsync-area">
|
||||
<div id="fxsync-header">
|
||||
<p data-l10n-id="fxsync">Firefox Sync</p>
|
||||
</div>
|
||||
<div id="fxsync-disabled">
|
||||
<smart-button id="fxsync-sign-in-button" type="rounded-corner-text" data-l10n-id="fxsync-sign-in"></smart-button>
|
||||
<p data-l10n-id="fxsync-sign-in-to-sync-description"></p>
|
||||
<p data-l10n-id="fxsync-sign-in-to-sync-description-plus"></p>
|
||||
</div>
|
||||
<div id="fxsync-enabled" hidden>
|
||||
<span id="fxsync-signed-in-as" data-l10n-id="fxsync-signed-in-as"></span>
|
||||
<smart-button id="fxsync-sync-now-button" type="rounded-corner-text" data-l10n-id="fxsync-sync-now"></smart-button>
|
||||
<smart-button id="fxsync-sign-out-button" type="rounded-corner-text" data-l10n-id="fxsync-sign-out"></smart-button>
|
||||
<ul class="settings-horizontal-list">
|
||||
<li class="settings-horizontal-list-item">
|
||||
<input type="checkbox" id="fxsync-collection-bookmarks" class="settings-list-checkbox"></input>
|
||||
<label for="fxsync-collection-bookmarks"></label>
|
||||
<span data-l10n-id="fxsync-collections-bookmarks"></span>
|
||||
</li>
|
||||
<li class="settings-horizontal-list-item">
|
||||
<input type="checkbox" id="fxsync-collection-history" class="settings-list-checkbox"></input>
|
||||
<label for="fxsync-collection-history"></label>
|
||||
<span data-l10n-id="fxsync-collections-history"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="fine-print"><a id="fxsync-tos" data-l10n-id="fxsync-tos"></a></p>
|
||||
<p class="fine-print"><a id="fxsync-privacy" data-l10n-id="fxsync-privacy"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
ENDIF_FIREFOX_SYNC-->
|
||||
|
||||
<div id="settings-dialog" class="hidden">
|
||||
<div id="settings-dialog-homepage" class="hidden">
|
||||
<div id="settings-dialog-homepage-header">
|
||||
<p id="settings-dialog-homepage-header-title" data-l10n-id="WB_LT_SET_AS_HOMEPAGE">Set as Homepage</p>
|
||||
</div>
|
||||
<form id="settings-dialog-homepage-input">
|
||||
<input id="settings-dialog-homepage-input-area" value="" type="url">
|
||||
<div class="input-line"></div>
|
||||
<div id="settings-dialog-homepage-clear"></div>
|
||||
</form>
|
||||
<button id="settings-dialog-homepage-default" data-l10n-id="WB_LT_RESTORE_TO_DEFAULT">Restore to default</button>
|
||||
<div id="settings-dialog-homepage-button">
|
||||
<button id="settings-dialog-homepage-cancel" data-l10n-id="LT_CANCEL">Cancel</button>
|
||||
<button id="settings-dialog-homepage-ok" data-l10n-id="LT_WB_OK">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-dialog-search" class="hidden">
|
||||
<div id="settings-dialog-search-header">
|
||||
<p id="settings-dialog-search-header-title" data-l10n-id="WB_LT_SETTINGS_SEARCH_ENGINE">Search Engine</p>
|
||||
</div>
|
||||
<div id="settings-dialog-search-select">
|
||||
<div id="settings-dialog-search-select-list"></div>
|
||||
</div>
|
||||
<div id="settings-dialog-search-button">
|
||||
<button id="settings-dialog-search-cancel" data-l10n-id="LT_CANCEL">Cancel</button>
|
||||
<button id="settings-dialog-search-ok" data-l10n-id="LT_WB_OK">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-banner-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- loading -->
|
||||
<gaia-loading id="loading-icon">
|
||||
<div class="circle circle-1"></div>
|
||||
<div class="circle circle-2"></div>
|
||||
</gaia-loading>
|
||||
|
||||
<!-- zoom banner -->
|
||||
<div id="zoom-banner-message-block">
|
||||
<div id="zoom-banner-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- pan cursor mode button -->
|
||||
<div id="mode-button-block" data-mode="cursor" data-tips="WB_LT_PRESS_COLOR_KEY">
|
||||
<div id="mode-button-panel">
|
||||
<button id="pan-cursor-button"></button>
|
||||
<h2 id="mode-button-title" data-l10n-id="WB_LT_CURSOR_MODE">Cursor Mode</h2>
|
||||
<div id="pan-cursor-button-block" data-colorbar=""></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pan-cursor-banner-message" data-l10n-id="WB_LT_SWITCH_PAN_MODE">You have switched to Pan Mode</div>
|
||||
|
||||
<!-- search result -->
|
||||
<div id="search-result">
|
||||
<div id="search-result-list"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="block-r"></div>
|
||||
<div id="block-l"></div>
|
||||
<div id="block-d"></div>
|
||||
<!-- scroll left right -->
|
||||
<div id="scroll-l"></div>
|
||||
<div id="scroll-r"></div>
|
||||
|
||||
<!-- Tooltip -->
|
||||
<div id="tooltip-block"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
412
gaia/tv_apps/browser/js/authentication_dialog.js
Executable file
|
|
@ -0,0 +1,412 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
/* exported AuthenticationDialog */
|
||||
|
||||
/**
|
||||
* Handles mozbrowserusernameandpasswordrequired event and show
|
||||
* authentication dialog.
|
||||
* @namespace AuthenticationDialog
|
||||
*/
|
||||
var AuthenticationDialog = {
|
||||
|
||||
_confirmed: {},
|
||||
focusElement: [],
|
||||
focusIndex: {x:0, y:0},
|
||||
|
||||
/** Get all DOM elements when inited. */
|
||||
getAllElements: function ad_getAllElements() {
|
||||
|
||||
var toCamelCase = function toCamelCase(str) {
|
||||
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
var elementIDs = [
|
||||
'http-authentication-dialog',
|
||||
'http-authentication-username', 'http-authentication-username-clear',
|
||||
'http-authentication-password', 'http-authentication-password-clear',
|
||||
'http-authentication-username-area', 'http-authentication-password-area',
|
||||
'http-authentication-message',
|
||||
'http-authentication-ok', 'http-authentication-cancel'];
|
||||
|
||||
// Loop and add element with camel style name to Modal Dialog attribute.
|
||||
elementIDs.forEach(function createElementRef(name) {
|
||||
this[toCamelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
currentEvents: {},
|
||||
|
||||
/** Initialization. Get elements and add listeners. */
|
||||
init: function ad_init(bindToWindow) {
|
||||
// Get all elements initially.
|
||||
this.getAllElements();
|
||||
|
||||
this.boundToWindow = bindToWindow || false;
|
||||
|
||||
this.httpAuthenticationUsername.addEventListener('submit',
|
||||
this.dialogInputSubmit.bind(this));
|
||||
this.httpAuthenticationPassword.addEventListener('submit',
|
||||
this.dialogInputSubmit.bind(this));
|
||||
|
||||
this.httpAuthenticationUsernameArea.addEventListener('blur',
|
||||
this.dialogInputAreaBlur.bind(this));
|
||||
this.httpAuthenticationPasswordArea.addEventListener('blur',
|
||||
this.dialogInputAreaBlur.bind(this));
|
||||
|
||||
this.httpAuthenticationUsernameArea.addEventListener('focus',
|
||||
this.dialogUsernameAreaFocus.bind(this));
|
||||
this.httpAuthenticationPasswordArea.addEventListener('focus',
|
||||
this.dialogPasswordAreaFocus.bind(this));
|
||||
|
||||
this.httpAuthenticationUsernameClear.addEventListener('mouseup',
|
||||
this.clearUsername.bind(this));
|
||||
this.httpAuthenticationUsernameClear.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
this.httpAuthenticationUsernameClear.tabIndex = 0;
|
||||
|
||||
this.httpAuthenticationPasswordClear.addEventListener('mouseup',
|
||||
this.clearPassword.bind(this));
|
||||
this.httpAuthenticationPasswordClear.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
this.httpAuthenticationPasswordClear.tabIndex = 0;
|
||||
|
||||
this.httpAuthenticationOk.addEventListener('mouseup', this);
|
||||
this.httpAuthenticationOk.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
this.httpAuthenticationOk.tabIndex = 0;
|
||||
|
||||
this.httpAuthenticationCancel.addEventListener('mouseup', this);
|
||||
this.httpAuthenticationCancel.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
this.httpAuthenticationCancel.tabIndex = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Default event handler for mozbrowser event and click event.
|
||||
* @param {Event} evt
|
||||
* @param {String} origin Tab ID
|
||||
*/
|
||||
handleEvent: function ad_handleEvent(evt, origin) {
|
||||
switch (evt.type) {
|
||||
case 'mozbrowserusernameandpasswordrequired':
|
||||
evt.preventDefault();
|
||||
this.currentEvents[origin] = evt;
|
||||
|
||||
// Show authentication dialog only if
|
||||
// the frame is currently displayed.
|
||||
this.show(origin);
|
||||
break;
|
||||
|
||||
case 'mouseup':
|
||||
if (evt.currentTarget === this.httpAuthenticationCancel) {
|
||||
this.focusIndex.x = 2;
|
||||
this.focusIndex.y = 1;
|
||||
this.cancelHandler();
|
||||
} else {
|
||||
this.focusIndex.x = 2;
|
||||
this.focusIndex.y = 0;
|
||||
this.confirmHandler();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog and set message.
|
||||
* @param {String} origin Tab ID
|
||||
*/
|
||||
show: function ad_show(origin) {
|
||||
this.currentOrigin = origin;
|
||||
var evt = this.currentEvents[origin];
|
||||
|
||||
this.httpAuthenticationDialog.classList.remove('hidden');
|
||||
|
||||
// XXX: We don't have a better way to detect login failed.
|
||||
if (this._confirmed[origin]) {
|
||||
//this.httpAuthenticationMessage.setAttribute('data-l10n-id',
|
||||
//'the-username-or-password-is-incorrect');
|
||||
//this.httpAuthenticationMessage.classList.add('error');
|
||||
} else {
|
||||
navigator.mozL10n.setAttributes(
|
||||
this.httpAuthenticationMessage,
|
||||
'WB_LT_AUTH_MESSAGE',
|
||||
{
|
||||
host: evt.detail.host,
|
||||
msg: evt.detail.realm
|
||||
}
|
||||
);
|
||||
/*
|
||||
navigator.mozL10n.setAttributes(this.httpAuthenticationMessage,
|
||||
'http-authentication-message',
|
||||
{ host: evt.detail.host });
|
||||
*/
|
||||
this.httpAuthenticationMessage.classList.remove('error');
|
||||
}
|
||||
|
||||
Browser.switchCursorMode(false);
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
this.httpAuthenticationUsername.classList.remove('input');
|
||||
this.httpAuthenticationPassword.classList.remove('input');
|
||||
|
||||
this.httpAuthenticationUsernameArea.value = '';
|
||||
this.httpAuthenticationPasswordArea.value = '';
|
||||
this.httpAuthenticationUsernameArea.focus();
|
||||
|
||||
this.focusElement[0] = [];
|
||||
this.focusElement[0][0] = this.httpAuthenticationUsernameArea;
|
||||
this.focusElement[0][1] = this.httpAuthenticationUsernameClear;
|
||||
this.focusElement[1] = [];
|
||||
this.focusElement[1][0] = this.httpAuthenticationPasswordArea;
|
||||
this.focusElement[1][1] = this.httpAuthenticationPasswordClear;
|
||||
this.focusElement[2] = [];
|
||||
this.focusElement[2][0] = this.httpAuthenticationOk;
|
||||
this.focusElement[2][1] = this.httpAuthenticationCancel;
|
||||
this.focusIndex.x = 0;
|
||||
this.focusIndex.y = 0;
|
||||
},
|
||||
|
||||
/** Hide authentication dialog. */
|
||||
hide: function ad_hide() {
|
||||
var evt = this.currentEvents[this.currentOrigin];
|
||||
if (!evt) {
|
||||
return;
|
||||
}
|
||||
this.httpAuthenticationDialog.classList.add('hidden');
|
||||
this.currentOrigin = null;
|
||||
},
|
||||
|
||||
clearUsername: function ad_clearUsername() {
|
||||
this.focusIndex.x = 0;
|
||||
this.focusIndex.y = 1;
|
||||
this.httpAuthenticationUsernameArea.value = '';
|
||||
},
|
||||
|
||||
clearPassword: function ad_clearPassword() {
|
||||
this.focusIndex.x = 1;
|
||||
this.focusIndex.y = 1;
|
||||
this.httpAuthenticationPasswordArea.value = '';
|
||||
},
|
||||
|
||||
dialogButtonKeyup: function ad_dialogButtonKeyup(evt) {
|
||||
if( evt ) evt.preventDefault();
|
||||
switch( evt.keyCode ) {
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
var elm = document.activeElement;
|
||||
elm.classList.remove('active');
|
||||
var dEvt = document.createEvent("MouseEvents");
|
||||
dEvt.initMouseEvent("mouseup", true, true, window, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, elm);
|
||||
elm.dispatchEvent( dEvt );
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
dialogInputSubmit: function ad_dialogInputSubmit(evt) {
|
||||
if( evt ) evt.preventDefault();
|
||||
this.dialogInputAreaBlur(evt);
|
||||
},
|
||||
|
||||
dialogInputAreaBlur: function ad_dialogInputBlur(evt) {
|
||||
if(Awesomescreen.pointerImg.style.display == 'none') {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
}
|
||||
if (evt.currentTarget === this.httpAuthenticationUsernameArea) {
|
||||
this.focusIndex.x = 0;
|
||||
this.focusIndex.y = 0;
|
||||
this.httpAuthenticationUsername.classList.remove('input');
|
||||
this.httpAuthenticationUsername.classList.add('exfocus');
|
||||
} else if(evt.currentTarget === this.httpAuthenticationPasswordArea) {
|
||||
this.focusIndex.x = 1;
|
||||
this.focusIndex.y = 0;
|
||||
this.httpAuthenticationPassword.classList.remove('input');
|
||||
this.httpAuthenticationPassword.classList.add('exfocus');
|
||||
}
|
||||
Awesomescreen.focusImgFunc(this.focusElement[this.focusIndex.x][this.focusIndex.y]);
|
||||
},
|
||||
|
||||
dialogUsernameAreaFocus: function ad_dialogUsernameAreaFocus(evt) {
|
||||
if( evt ) evt.preventDefault();
|
||||
this.httpAuthenticationUsername.classList.add('input');
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
},
|
||||
|
||||
dialogPasswordAreaFocus: function ad_dialogPasswordAreaFocus(evt) {
|
||||
if( evt ) evt.preventDefault();
|
||||
this.httpAuthenticationPassword.classList.add('input');
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
},
|
||||
|
||||
/**
|
||||
* When user clicks OK button on authentication dialog,
|
||||
* authenticate user.
|
||||
*/
|
||||
confirmHandler: function ad_confirmHandler() {
|
||||
var evt = this.currentEvents[this.currentOrigin];
|
||||
evt.detail.authenticate(this.httpAuthenticationUsernameArea.value,
|
||||
this.httpAuthenticationPasswordArea.value);
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
this.httpAuthenticationDialog.classList.add('hidden');
|
||||
|
||||
// To remember we had ever logged in.
|
||||
this._confirmed[this.currentOrigin] = true;
|
||||
|
||||
delete this.currentEvents[this.currentOrigin];
|
||||
Browser.switchCursorMode(true);
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
},
|
||||
|
||||
/**
|
||||
* When user clicks cancel button on authentication dialog or
|
||||
* when the user try to escape the dialog with the escape key,
|
||||
* cancel authentication.
|
||||
*/
|
||||
cancelHandler: function ad_cancelHandler() {
|
||||
var evt = this.currentEvents[this.currentOrigin];
|
||||
|
||||
evt.detail.cancel();
|
||||
|
||||
if (this._confirmed[this.currentOrigin]) {
|
||||
delete this._confirmed[this.currentOrigin];
|
||||
}
|
||||
|
||||
delete this.currentEvents[this.currentOrigin];
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
this.httpAuthenticationDialog.classList.add('hidden');
|
||||
Browser.switchCursorMode(true);
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the specified tab currently has any event.
|
||||
* @param {String} origin Tab ID
|
||||
*/
|
||||
originHasEvent: function(origin) {
|
||||
return origin in this.currentEvents;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear events of the specified tab ID.
|
||||
* @param {String} origin Tab ID
|
||||
*/
|
||||
clear: function ad_clear(origin) {
|
||||
if (this.currentEvents[origin]) {
|
||||
delete this.currentEvents[origin];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Authentication dialog displays a confirmation.
|
||||
*/
|
||||
isDisplayed: function is_displayed() {
|
||||
return !this.httpAuthenticationDialog.classList.contains('hidden');
|
||||
},
|
||||
|
||||
moveFocus: function ad_moveFocus(xd, yd) {
|
||||
if(xd < 0 || yd < 0) {
|
||||
return;
|
||||
}
|
||||
if( (!this.focusElement[xd]) || (!this.focusElement[xd][yd]) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var opt = null;
|
||||
var flg = false;
|
||||
this.focusIndex.x = xd;
|
||||
this.focusIndex.y = yd;
|
||||
if(Awesomescreen.pointerImg.style.display == 'none') {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
flg = true;
|
||||
}
|
||||
document.activeElement.blur();
|
||||
if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-username-area') {
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
if( !this.httpAuthenticationUsername.classList.contains('exfocus') ) {
|
||||
this.httpAuthenticationUsername.classList.add('exfocus');
|
||||
}
|
||||
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-username-clear') {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
if( !this.httpAuthenticationUsername.classList.contains('exfocus') ) {
|
||||
this.httpAuthenticationUsername.classList.add('exfocus');
|
||||
}
|
||||
if( flg ) opt = Awesomescreen.DEFAULT_EXCLEAR;
|
||||
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-password-area') {
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
if( !this.httpAuthenticationPassword.classList.contains('exfocus') ) {
|
||||
this.httpAuthenticationPassword.classList.add('exfocus');
|
||||
}
|
||||
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-password-clear') {
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
if( !this.httpAuthenticationPassword.classList.contains('exfocus') ) {
|
||||
this.httpAuthenticationPassword.classList.add('exfocus');
|
||||
}
|
||||
if( flg ) opt = Awesomescreen.DEFAULT_EXCLEAR;
|
||||
} else {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
this.httpAuthenticationUsername.classList.remove('exfocus');
|
||||
this.httpAuthenticationPassword.classList.remove('exfocus');
|
||||
}
|
||||
Awesomescreen.focusImgFunc(this.focusElement[this.focusIndex.x][this.focusIndex.y], opt);
|
||||
},
|
||||
|
||||
handleKeyEvent: function ad_handleKeyEvent(evt) {
|
||||
if( !this.isDisplayed() ) return false;
|
||||
if( document.activeElement.nodeName == 'INPUT' ) {
|
||||
return true;
|
||||
}
|
||||
switch( evt.keyCode ) {
|
||||
case KeyEvent.DOM_VK_LEFT :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x, this.focusIndex.y-1);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RIGHT :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x, this.focusIndex.y+1);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_UP :
|
||||
evt.preventDefault();
|
||||
if( this.focusIndex.x == 2 ) {
|
||||
this.moveFocus(this.focusIndex.x-1, 0);
|
||||
} else {
|
||||
this.moveFocus(this.focusIndex.x-1, this.focusIndex.y);
|
||||
}
|
||||
break;
|
||||
case KeyEvent.DOM_VK_DOWN :
|
||||
evt.preventDefault();
|
||||
if( this.focusIndex.x == 1 ) {
|
||||
this.moveFocus(this.focusIndex.x+1, 0);
|
||||
} else {
|
||||
this.moveFocus(this.focusIndex.x+1, this.focusIndex.y);
|
||||
}
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
evt.preventDefault();
|
||||
if((this.focusIndex.x == 0 && this.focusIndex.y == 0) ||
|
||||
(this.focusIndex.x == 1 && this.focusIndex.y == 0)) {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
} else {
|
||||
document.activeElement.classList.remove('active');
|
||||
document.activeElement.classList.add('active');
|
||||
}
|
||||
break;
|
||||
case KeyEvent.DOM_VK_BACK_SPACE :
|
||||
this.cancelHandler();
|
||||
return true;
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
3216
gaia/tv_apps/browser/js/awesomescreen.js
Executable file
239
gaia/tv_apps/browser/js/bookmarkStore.js
Executable file
|
|
@ -0,0 +1,239 @@
|
|||
/* globals BrowserDB, SyncBrowserDB, SyncManagerBridge */
|
||||
'use strict';
|
||||
|
||||
(function(exports){
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
/**
|
||||
* The root folder id of synced bookmark data.
|
||||
* @type {String}
|
||||
*/
|
||||
const SYNC_BOOKMARK_ROOT_FOLDER_ID = 'places';
|
||||
/**
|
||||
* The mobile folder id of synced bookmark data.
|
||||
* @type {String}
|
||||
*/
|
||||
const SYNC_BOOKMARK_MOBILE_FOLDER_ID = 'mobile';
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
// TODO: for the live time bookmark data loading task.
|
||||
var BookmarkStore = {
|
||||
isSynced: false,
|
||||
|
||||
cache: [],
|
||||
|
||||
currentFolder : null,
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
syncBookmarkRootFolderCache: {
|
||||
id: SYNC_BOOKMARK_ROOT_FOLDER_ID,
|
||||
title: '',
|
||||
type: 'folder',
|
||||
readOnly: true
|
||||
},
|
||||
|
||||
syncBookmarkMobileFolderCache: {
|
||||
id: SYNC_BOOKMARK_MOBILE_FOLDER_ID,
|
||||
title: '',
|
||||
type: 'folder',
|
||||
readOnly: true
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
init: function () {
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
var l10nPromises = [];
|
||||
l10nPromises
|
||||
.push(navigator.mozL10n.formatValue('fxsync-desktop-bookmarks'));
|
||||
l10nPromises
|
||||
.push(navigator.mozL10n.formatValue('fxsync-mobile-bookmarks'));
|
||||
Promise.all(l10nPromises).then(
|
||||
([l10nFxsyncDesktopBookmarks, l10nFxsyncMobileBookmarks]) => {
|
||||
this.syncBookmarkRootFolderCache.title = l10nFxsyncDesktopBookmarks;
|
||||
this.syncBookmarkMobileFolderCache.title = l10nFxsyncMobileBookmarks;
|
||||
}
|
||||
);
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
},
|
||||
|
||||
reset: function(cb) {
|
||||
this.cache = [];
|
||||
this.currentFolder = null;
|
||||
|
||||
return new Promise(resolve => {
|
||||
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
resolve();
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
SyncManagerBridge.getInfo().then(message => {
|
||||
if (message.state === 'enabled' || message.state === 'syncing') {
|
||||
this.isSynced = true;
|
||||
} else {
|
||||
this.isSynced = false;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
getByRange: function(start, num, folderId, cb) {
|
||||
var self = this;
|
||||
function getCaches(){
|
||||
var length = (start + num) > self.cache.length ?
|
||||
(self.cache.length - start) : (start + num);
|
||||
var result = [];
|
||||
|
||||
for(var i = start; i < length; i++) {
|
||||
result.push(self.cache[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if(folderId !== this.currentFolder) {
|
||||
this.currentFolder = folderId;
|
||||
this.updateCache().then(() => {
|
||||
cb(getCaches());
|
||||
});
|
||||
} else {
|
||||
cb(getCaches());
|
||||
}
|
||||
},
|
||||
|
||||
getByIndex: function(index, folderId, cb) {
|
||||
var self = this;
|
||||
function getCache(){
|
||||
var result = null;
|
||||
if(index >= 0 && index < self.cache.length) {
|
||||
result = self.cache[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if(folderId !== this.currentFolder) {
|
||||
this.currentFolder = folderId;
|
||||
this.updateCache().then(() => {
|
||||
cb(getCache());
|
||||
});
|
||||
} else {
|
||||
cb(getCache());
|
||||
}
|
||||
},
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
_createRootFolderCache: function () {
|
||||
var self = this;
|
||||
// We query remote bookmarks by parentid with
|
||||
// SYNC_BOOKMARK_ROOT_FOLDER_ID for checking if the indexedDB has
|
||||
// remote bookmark data. If there is any remote data, we create a
|
||||
// folder item to show the synced bookmark.
|
||||
function getPlacesBookmarks() {
|
||||
return new Promise(resolve => {
|
||||
SyncBrowserDB.getBookmark({
|
||||
parentid: SYNC_BOOKMARK_ROOT_FOLDER_ID
|
||||
}, syncPlacesBookmarks => {
|
||||
if(syncPlacesBookmarks.length > 0) {
|
||||
self.cache.push(self.syncBookmarkRootFolderCache);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If there is mobile folder and also has children in it,
|
||||
// create a folder item to show the synced mobile bookmark
|
||||
function getMobileBookmarks() {
|
||||
return new Promise(resolve => {
|
||||
SyncBrowserDB.getBookmark({
|
||||
parentid: SYNC_BOOKMARK_MOBILE_FOLDER_ID
|
||||
}, syncMobileBookmarks => {
|
||||
if(syncMobileBookmarks.length > 0) {
|
||||
self.cache.push(self.syncBookmarkMobileFolderCache);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// get bookmark data from origin indexdDB
|
||||
function getLocalBookmarks() {
|
||||
return new Promise(resolve => {
|
||||
BrowserDB.getBookmarks(localBookmarks => {
|
||||
if(localBookmarks.length > 0) {
|
||||
self.cache = self.cache.concat(localBookmarks);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return getPlacesBookmarks()
|
||||
.then(getMobileBookmarks)
|
||||
.then(getLocalBookmarks);
|
||||
},
|
||||
|
||||
_updateFolderCache: function () {
|
||||
return new Promise(resolve => {
|
||||
SyncBrowserDB.getBookmark({
|
||||
parentid: this.currentFolder
|
||||
}, syncBookmark => {
|
||||
var length = syncBookmark.length,
|
||||
bookmark = null;
|
||||
for(var i = 0; i < length; i++) {
|
||||
bookmark = syncBookmark[i];
|
||||
//hide mobile folder in sync bookmark folder
|
||||
if(this.currentFolder === SYNC_BOOKMARK_ROOT_FOLDER_ID &&
|
||||
bookmark.id === SYNC_BOOKMARK_MOBILE_FOLDER_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// XXX: Now we only support two types: folder and bookmark.
|
||||
// And the data from firefox sync can't be modified.
|
||||
if(bookmark.type === 'folder' && bookmark.title) {
|
||||
bookmark.readOnly = true;
|
||||
this.cache.push(bookmark);
|
||||
} else if (bookmark.type === 'bookmark') {
|
||||
bookmark.uri = bookmark.bmkUri;
|
||||
bookmark.readOnly = true;
|
||||
this.cache.push(bookmark);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
updateCache: function(){
|
||||
this.cache = [];
|
||||
return new Promise(resolve => {
|
||||
if(!this.isSynced) {
|
||||
// Get bookmark data from origin indexedDB
|
||||
BrowserDB.getBookmarks(bookmarks => {
|
||||
this.cache = bookmarks;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
resolve();
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
if(!this.currentFolder) {
|
||||
this._createRootFolderCache().then(resolve);
|
||||
} else {
|
||||
this._updateFolderCache().then(resolve);
|
||||
}
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.BookmarkStore = BookmarkStore;
|
||||
})(window);
|
||||
2030
gaia/tv_apps/browser/js/browser_db.js
Executable file
607
gaia/tv_apps/browser/js/browser_dialog.js
Executable file
|
|
@ -0,0 +1,607 @@
|
|||
/* global Awesomescreen */
|
||||
/* global Browser */
|
||||
/* global BrowserDB */
|
||||
/* global KeyEvent */
|
||||
/* global Settings */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The dialog listen to mozbrowsershowmodalprompt event.
|
||||
* (alert/confirm/prompt)
|
||||
*
|
||||
* @namespace Dialog
|
||||
*/
|
||||
var BrowserDialog = {
|
||||
// Dialog Event
|
||||
dialogEvt: null,
|
||||
browserDialogInputArea: null,
|
||||
PORT_MIN: 0,
|
||||
PORT_MAX: 65535,
|
||||
focusElement: [],
|
||||
focusIndex: {x:0, y:0},
|
||||
defaultFocusIndex: {x:0, y:0},
|
||||
deferredActions: new Map(),
|
||||
|
||||
/** Get all elements when inited. */
|
||||
getAllElements: function dialog_getAllElements() {
|
||||
var toCamelCase = function toCamelCase(str) {
|
||||
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
var elementIDs = [
|
||||
'browser-dialog-base', 'browser-dialog',
|
||||
'browser-dialog-title', 'browser-dialog-msg',
|
||||
'browser-dialog-input',
|
||||
'browser-dialog-input-area',
|
||||
'browser-dialog-input-clear',
|
||||
'browser-dialog-button',
|
||||
'browser-dialog-button1', 'browser-dialog-button2',
|
||||
];
|
||||
|
||||
// Loop and add element with camel style name to Dialog attribute.
|
||||
elementIDs.forEach(function createElementRef(name) {
|
||||
this[toCamelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/** Initialization. Get DOM elements and add listeners. */
|
||||
init: function dialog_init() {
|
||||
// Get all elements initially.
|
||||
this.getAllElements();
|
||||
|
||||
this.browserDialogBase.addEventListener('mouseup',
|
||||
this.dialogBaseClick.bind(this));
|
||||
|
||||
// Add event listener(dialog)
|
||||
this.browserDialogButton1.addEventListener('mouseup',
|
||||
this.dialogButton1.bind(this));
|
||||
this.browserDialogButton1.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
|
||||
this.browserDialogButton2.addEventListener('mouseup',
|
||||
this.dialogButton2.bind(this));
|
||||
this.browserDialogButton2.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
|
||||
this.browserDialogInput.addEventListener('submit',
|
||||
this.dialogInput.bind(this));
|
||||
this.browserDialogInputClear.addEventListener('mouseup',
|
||||
this.dialogInputClear.bind(this));
|
||||
this.browserDialogInputClear.addEventListener('keyup',
|
||||
this.dialogButtonKeyup.bind(this));
|
||||
|
||||
this.browserDialogInputArea.addEventListener('blur',
|
||||
this.dialogInputAreaBlur.bind(this));
|
||||
this.browserDialogInputArea.addEventListener('focus',
|
||||
this.dialogInputAreaFocus.bind(this));
|
||||
this.browserDialogInputArea.addEventListener('mousedown',
|
||||
this.dialogInputAreaClick.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* create Dialog
|
||||
*/
|
||||
createDialog: function dialog_createDialog(type, evt) {
|
||||
var opt;
|
||||
|
||||
this.cancelDialog();
|
||||
if( evt != null ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
this.dialogEvt = evt;
|
||||
this.browserDialogBase.classList.remove('hide');
|
||||
this.browserDialogBase.dataset.type = type;
|
||||
|
||||
var promise;
|
||||
switch(type) {
|
||||
case 'del_cookie':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: 'WB_LT_CONFIRM_DELETE_COOKIES',
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: 'WB_LT_CLEAR'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'clear_history':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: 'WB_LT_CLEAR_ALL_HISTORY',
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: 'WB_LT_CLEAR'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'max_bookmark':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: 'WB_LT_BOOKMARK_ERROR_1',
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: null
|
||||
};
|
||||
break;
|
||||
|
||||
case 'alert':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: {raw: evt.detail.message},
|
||||
bt1: 'LT_WB_OK',
|
||||
bt2: null
|
||||
};
|
||||
break;
|
||||
|
||||
case 'prompt':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: {raw: evt.detail.message},
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: 'LT_WB_OK'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'confirm':
|
||||
opt = {
|
||||
title: null,
|
||||
msg: {raw: evt.detail.message},
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: 'LT_WB_OK'
|
||||
};
|
||||
break;
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
case 'signout_confirm':
|
||||
opt = {
|
||||
title: 'fxsync-confirm-sign-out-title',
|
||||
msg: 'fxsync-confirm-sign-out-message',
|
||||
bt1: 'LT_CANCEL',
|
||||
bt2: 'fxsync-sign-out'
|
||||
};
|
||||
var deferred = {};
|
||||
deferred.promise = new Promise(function(resolve, reject) {
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
});
|
||||
promise = deferred.promise;
|
||||
this.deferredActions.set('signout_confirm', deferred);
|
||||
break;
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
Browser.switchCursorMode(false);
|
||||
|
||||
// title
|
||||
if (opt.title) {
|
||||
this.browserDialogTitle.setAttribute('data-l10n-id', opt.title);
|
||||
} else {
|
||||
this.browserDialogTitle.removeAttribute('data-l10n-id');
|
||||
this.browserDialogTitle.textContent = '';
|
||||
}
|
||||
|
||||
// msg
|
||||
if(opt.msg) {
|
||||
if (typeof opt.msg === 'string') {
|
||||
this.browserDialogMsg.setAttribute('data-l10n-id', opt.msg);
|
||||
} else if (opt.msg.hasOwnProperty('raw')) {
|
||||
this.browserDialogMsg.innerHTML = opt.msg.raw.replace(/\\n/g, '<br>');
|
||||
}
|
||||
} else {
|
||||
this.browserDialogMsg.removeAttribute('data-l10n-id');
|
||||
this.browserDialogMsg.textContent = '';
|
||||
}
|
||||
|
||||
var countIndex = 0;
|
||||
this.focusElement = [];
|
||||
this.focusElement[0] = [];
|
||||
this.focusElement[1] = [];
|
||||
this.defaultFocusIndex = {x:0, y:0};
|
||||
// button1
|
||||
if(opt.bt1) {
|
||||
this.browserDialogButton1.blur();
|
||||
this.browserDialogButton1.setAttribute('data-l10n-id', opt.bt1);
|
||||
this.browserDialogButton1.dataset.type = type;
|
||||
this.browserDialogButton1.classList.add('visible');
|
||||
}
|
||||
|
||||
// button2
|
||||
if(opt.bt2) {
|
||||
this.browserDialogButton2.blur();
|
||||
this.browserDialogButton2.setAttribute('data-l10n-id', opt.bt2);
|
||||
this.browserDialogButton2.dataset.type = type;
|
||||
this.browserDialogButton2.classList.add('visible');
|
||||
}
|
||||
|
||||
var width = Browser.SCREEN_WIDTH;
|
||||
if( Browser.sideBlock.dataset.sidebar == 'true' ) {
|
||||
width -= Browser.SIDE_WINDOW_WIDTH;
|
||||
}
|
||||
|
||||
if(type == 'prompt') {
|
||||
this.browserDialogInput.style.display = 'block';
|
||||
this.browserDialogInput.classList.remove('exfocus');
|
||||
this.browserDialogInput.classList.remove('input');
|
||||
this.browserDialogInputClear.tabIndex = 0;
|
||||
countIndex = 0;
|
||||
this.focusElement[1][countIndex++] = this.browserDialogInputArea;
|
||||
this.focusElement[1][countIndex++] = this.browserDialogInputClear;
|
||||
//this.browserDialogInput.insertBefore(this.browserDialogInputArea,
|
||||
// this.browserDialogInputClear);
|
||||
this.browserDialogInputArea.type = 'text';
|
||||
this.browserDialogInputArea.value = '';
|
||||
} else {
|
||||
this.browserDialogInput.style.display = 'none';
|
||||
}
|
||||
|
||||
// initialize position
|
||||
countIndex = 0;
|
||||
if(opt.bt1 && opt.bt2) {
|
||||
this.defaultFocusIndex.x = 0;
|
||||
this.defaultFocusIndex.y = 0;
|
||||
this.focusIndex.x = 0;
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
if(type === 'signout_confirm') {
|
||||
this.focusIndex.y = 0;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton1;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton2;
|
||||
} else {
|
||||
this.focusIndex.y = 1;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton2;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton1;
|
||||
}
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
this.focusIndex.y = 1;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton2;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton1;
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
} else if(opt.bt1) {
|
||||
this.defaultFocusIndex.x = 0;
|
||||
this.defaultFocusIndex.y = 0;
|
||||
this.focusIndex.x = 0;
|
||||
this.focusIndex.y = 0;
|
||||
this.focusElement[0][countIndex++] = this.browserDialogButton1;
|
||||
}
|
||||
Awesomescreen.focusImgFunc(
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y]);
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
return promise;
|
||||
},
|
||||
|
||||
isDisplayed: function dialog_isDisplayed() {
|
||||
return !this.browserDialogBase.classList.contains('hide');
|
||||
},
|
||||
|
||||
dialogBaseClick: function dialog_dialogBaseClick(evt) {
|
||||
if( evt ) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
dialogButtonKeyup: function dialog_dialogButton1Keyup(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
switch( evt.keyCode ) {
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
var elm = document.activeElement;
|
||||
elm.classList.remove('active');
|
||||
var dEvt = document.createEvent('MouseEvents');
|
||||
dEvt.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, elm);
|
||||
elm.dispatchEvent( dEvt );
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
completeDeferredAction: function(type, resolution) {
|
||||
if (!this.deferredActions.has(type)) {
|
||||
return;
|
||||
}
|
||||
this.deferredActions.get(type)[resolution]();
|
||||
this.deferredActions.delete(type);
|
||||
},
|
||||
|
||||
dialogButton1: function dialog_dialogButton1(evt) {
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
var type = this.browserDialogButton1.dataset.type;
|
||||
|
||||
switch(type) {
|
||||
case 'del_cookie':
|
||||
case 'clear_history':
|
||||
case 'max_bookmark':
|
||||
case 'alert':
|
||||
case 'prompt':
|
||||
case 'confirm':
|
||||
case 'signout_confirm':
|
||||
this.dialogButton2End(evt.currentTarget);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
this.completeDeferredAction(type, 'reject');
|
||||
},
|
||||
|
||||
dialogButton2End: function dialog_dialogButton2End(target) {
|
||||
// Animation end event
|
||||
var end_event = (function() {
|
||||
target.removeEventListener('transitionend', end_event, false);
|
||||
BrowserDialog.cancelDialog();
|
||||
});
|
||||
target.addEventListener('transitionend', end_event, false);
|
||||
},
|
||||
|
||||
dialogButton2: function dialog_dialogButton2(evt) {
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
this.argEvt = evt.currentTarget;
|
||||
var type = this.browserDialogButton2.dataset.type;
|
||||
switch (type) {
|
||||
case 'del_cookie':
|
||||
var request = navigator.mozApps.getSelf();
|
||||
request.onsuccess = function() {
|
||||
var rtn = request.result.clearBrowserData();
|
||||
rtn.onsuccess = function() {
|
||||
//BrowserDialog.cancelDialog();
|
||||
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
|
||||
};
|
||||
rtn.onerror = function() {
|
||||
//BrowserDialog.cancelDialog();
|
||||
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
|
||||
Settings.clearCookieFailed();
|
||||
};
|
||||
};
|
||||
request.onerror = function() {
|
||||
//BrowserDialog.cancelDialog();
|
||||
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
|
||||
Settings.clearCookieFailed();
|
||||
};
|
||||
break;
|
||||
|
||||
case 'clear_history':
|
||||
//BrowserDB.clearHistory(this.cancelDialog.bind(this));
|
||||
BrowserDB.clearHistory(
|
||||
function(){
|
||||
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
|
||||
}.bind(this));
|
||||
Awesomescreen.selectTopSites();
|
||||
break;
|
||||
|
||||
case 'prompt':
|
||||
if (this.dialogEvt != null) {
|
||||
this.dialogEvt.detail.returnValue = this.browserDialogInputArea.value;
|
||||
if (this.dialogEvt.detail.unblock) {
|
||||
this.dialogEvt.detail.unblock();
|
||||
}
|
||||
this.dialogEvt = null;
|
||||
}
|
||||
//this.cancelDialog();
|
||||
this.dialogButton2End(this.argEvt);
|
||||
break;
|
||||
|
||||
case 'confirm':
|
||||
if (this.dialogEvt != null) {
|
||||
this.dialogEvt.detail.returnValue = true;
|
||||
if (this.dialogEvt.detail.unblock) {
|
||||
this.dialogEvt.detail.unblock();
|
||||
}
|
||||
this.dialogEvt = null;
|
||||
}
|
||||
//this.cancelDialog();
|
||||
this.dialogButton2End(this.argEvt);
|
||||
break;
|
||||
|
||||
case 'signout_confirm':
|
||||
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.completeDeferredAction(type, 'resolve');
|
||||
},
|
||||
|
||||
dialogInput: function dialog_dialogInput(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
this.browserDialogInputArea.blur();
|
||||
},
|
||||
|
||||
dialogInputAreaFocus: function dialog_dialogInputAreaFocus(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
this.browserDialogInput.classList.add('input');
|
||||
this.browserDialogInput.classList.remove('exfocus');
|
||||
},
|
||||
|
||||
dialogInputClear: function dialog_dialogInputClear() {
|
||||
this.browserDialogInputArea.value = '';
|
||||
},
|
||||
|
||||
dialogInputAreaKeydown: function dialog_dialogInputKeydown(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
switch( evt.keyCode ) {
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
this.moveFocus(this.focusIndex.x+1, 0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
dialogInputAreaClick: function dialog_dialogInputAreaClick(evt) {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
|
||||
this.dialogInputAreaFocus();
|
||||
|
||||
this.focusIndex.x = 1;
|
||||
this.focusIndex.y = 0;
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
},
|
||||
dialogInputAreaBlur: function dialog_dialogInputBlur() {
|
||||
if(Awesomescreen.pointerImg.style.display == 'none') {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
}
|
||||
BrowserDialog.browserDialogInput.classList.remove('input');
|
||||
BrowserDialog.browserDialogInput.classList.add('exfocus');
|
||||
var self = this;
|
||||
setTimeout( function() {
|
||||
self.focusIndex.x = 1;
|
||||
self.focusIndex.y = 0;
|
||||
Awesomescreen.focusImgFunc(self.browserDialogInputArea);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* cancel confirm dialog
|
||||
*/
|
||||
cancelDialog: function dialog_cancelDialog() {
|
||||
if( !BrowserDialog.isDisplayed() ) {
|
||||
return;
|
||||
}
|
||||
switch( BrowserDialog.browserDialogButton1.dataset.type ) {
|
||||
case 'alert':
|
||||
case 'prompt':
|
||||
if( BrowserDialog.dialogEvt != null ) {
|
||||
BrowserDialog.dialogEvt.detail.returnValue = null;
|
||||
if( BrowserDialog.dialogEvt.detail.unblock ) {
|
||||
BrowserDialog.dialogEvt.detail.unblock();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'confirm':
|
||||
if( BrowserDialog.dialogEvt != null ) {
|
||||
BrowserDialog.dialogEvt.detail.returnValue = false;
|
||||
if( BrowserDialog.dialogEvt.detail.unblock ) {
|
||||
BrowserDialog.dialogEvt.detail.unblock();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if( BrowserDialog.dialogEvt != null ) {
|
||||
BrowserDialog.dialogEvt = null;
|
||||
}
|
||||
// hide dialog
|
||||
if( BrowserDialog.browserDialogInputArea ) {
|
||||
BrowserDialog.browserDialogInput.classList.remove('exfocus');
|
||||
BrowserDialog.browserDialogInput.classList.remove('input');
|
||||
}
|
||||
BrowserDialog.browserDialogBase.classList.add('hide');
|
||||
BrowserDialog.browserDialogButton1.classList.remove('visible');
|
||||
BrowserDialog.browserDialogButton2.classList.remove('visible');
|
||||
Browser.switchCursorMode(true);
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
},
|
||||
|
||||
moveFocus: function dialog_moveFocus(xd, yd) {
|
||||
if(!this.focusElement[xd]) {
|
||||
return;
|
||||
}
|
||||
if(( xd < 0 || yd < 0 ) || ( !this.focusElement[xd][yd] )) {
|
||||
if(( !this.browserDialogButton2.classList.contains('visible') ) &&
|
||||
( Awesomescreen.pointerImg.style.display !== 'block' )) {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
var x = this.defaultFocusIndex.x;
|
||||
var y = this.defaultFocusIndex.y;
|
||||
this.focusIndex.x = x;
|
||||
this.focusIndex.y = y;
|
||||
Awesomescreen.focusImgFunc(this.focusElement[x][y]);
|
||||
this.focusElement[x][y].focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.focusIndex.x = xd;
|
||||
this.focusIndex.y = yd;
|
||||
var opt = null;
|
||||
var flg = false;
|
||||
if(Awesomescreen.pointerImg.style.display == 'none') {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
flg = true;
|
||||
}
|
||||
document.activeElement.blur();
|
||||
Awesomescreen.focusImgFunc(
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y], opt);
|
||||
if(this.focusElement[this.focusIndex.x][this.focusIndex.y].nodeName ==
|
||||
'INPUT') {
|
||||
if( !this.browserDialogInput.classList.contains('exfocus') ) {
|
||||
this.browserDialogInput.classList.add('exfocus');
|
||||
}
|
||||
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id ==
|
||||
'browser-dialog-input-clear') {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
if( !this.browserDialogInput.classList.contains('exfocus') ) {
|
||||
this.browserDialogInput.classList.add('exfocus');
|
||||
}
|
||||
if( flg ) {
|
||||
opt = Awesomescreen.DEFAULT_EXCLEAR;
|
||||
}
|
||||
} else {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
this.browserDialogInput.classList.remove('exfocus');
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyEvent: function dialog_handleKeyEvent(evt) {
|
||||
if( !BrowserDialog.isDisplayed() ) {
|
||||
return false;
|
||||
}
|
||||
if( document.activeElement.nodeName == 'INPUT' ) {
|
||||
return true;
|
||||
}
|
||||
switch( evt.keyCode ) {
|
||||
case KeyEvent.DOM_VK_LEFT :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x, this.focusIndex.y-1);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RIGHT :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x, this.focusIndex.y+1);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_UP :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x+1, 0);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_DOWN :
|
||||
evt.preventDefault();
|
||||
this.moveFocus(this.focusIndex.x-1, this.defaultFocusIndex.y);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
evt.preventDefault();
|
||||
if(this.focusIndex.x === 1 && this.focusIndex.y === 0) {
|
||||
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
} else {
|
||||
document.activeElement.classList.remove('active');
|
||||
document.activeElement.classList.add('active');
|
||||
}
|
||||
break;
|
||||
case KeyEvent.DOM_VK_BACK_SPACE :
|
||||
BrowserDialog.cancelDialog();
|
||||
return true;
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
77
gaia/tv_apps/browser/js/connection_handler.js
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
/* global ConnectionManager, ConnectionHandler, Browser,
|
||||
UrlHelper, Awesomescreen */
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
var ConnectionHandler = function() {};
|
||||
|
||||
ConnectionHandler.prototype = {
|
||||
_active: false,
|
||||
_messageQueue: [],
|
||||
|
||||
init: function() {
|
||||
this._connectionManager = new ConnectionManager();
|
||||
this._connectionManager.init(['iac-webpage-open']);
|
||||
this._connectionManager.on('view', this.preHandleMessage.bind(this));
|
||||
},
|
||||
|
||||
preHandleMessage: function(detail) {
|
||||
if (this._active) {
|
||||
this._digestMessage();
|
||||
this.openPage(detail.url);
|
||||
} else {
|
||||
this._messageQueue.push(detail);
|
||||
}
|
||||
},
|
||||
|
||||
_digestMessage: function() {
|
||||
if (this._messageQueue.length !== 0) {
|
||||
this._messageQueue.forEach(function(detail){
|
||||
this.openPage(detail.url);
|
||||
}.bind(this));
|
||||
this._messageQueue = [];
|
||||
}
|
||||
},
|
||||
|
||||
openPage: function(url) {
|
||||
url = Browser.getUrlFromInput(url);
|
||||
if (Browser.currentInfo && Browser.currentInfo.url &&
|
||||
UrlHelper.isURL(Browser.currentInfo.url)) {
|
||||
var ev = {
|
||||
detail: {
|
||||
url: url,
|
||||
frameElement: null
|
||||
}
|
||||
};
|
||||
Awesomescreen.openNewTab(ev);
|
||||
} else {
|
||||
Browser.navigate(url);
|
||||
}
|
||||
this.bringToFront();
|
||||
},
|
||||
|
||||
bringToFront: function() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
var app = evt.target.result;
|
||||
if (app) {
|
||||
app.launch();
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this._active = true;
|
||||
this._digestMessage();
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
this._active = false;
|
||||
},
|
||||
};
|
||||
|
||||
exports.ConnectionHandler = ConnectionHandler;
|
||||
}(window));
|
||||
|
||||
var connectionHandler = new ConnectionHandler();
|
||||
connectionHandler.init();
|
||||
76
gaia/tv_apps/browser/js/fte.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global Browser */
|
||||
/* global FTEWizard */
|
||||
/* global ResourceLoader */
|
||||
/* global SyncManagerBridge */
|
||||
|
||||
// XXX: If Firefox sign in is available outside of the browser app, we'll have
|
||||
// to remove or redesign FTE.
|
||||
|
||||
'use strict';
|
||||
|
||||
(function (exports) {
|
||||
|
||||
if (localStorage.getItem('browserFTE.fteskip')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var FTE = {
|
||||
|
||||
fteContainer: null,
|
||||
|
||||
fteWizard: null,
|
||||
|
||||
onuninit: function () {},
|
||||
|
||||
init(options) {
|
||||
this.onuninit = options.onuninit;
|
||||
|
||||
ResourceLoader.loadByName('fte')
|
||||
.then(node => {
|
||||
document.body.appendChild(node);
|
||||
|
||||
this.fteContainer = document.getElementById('fte-container');
|
||||
|
||||
this.fteWizard = new FTEWizard('browserFTE');
|
||||
this.fteWizard.init({
|
||||
container: this.fteContainer,
|
||||
propagateKeyEvent: true,
|
||||
onfinish: () => {
|
||||
SyncManagerBridge.enable();
|
||||
this.uninit();
|
||||
},
|
||||
onskip: () => {
|
||||
this.uninit();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', this);
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent() {
|
||||
if (!document.hidden) {
|
||||
this.fteWizard.focus();
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
document.body.removeChild(this.fteContainer);
|
||||
document.removeEventListener('visibilitychange', this);
|
||||
ResourceLoader.unloadByName('fte');
|
||||
this.onuninit();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.FTE = FTE;
|
||||
exports.FTE.init({onuninit: () => {
|
||||
Browser.switchCursorMode(true);
|
||||
delete exports.FTE;
|
||||
}});
|
||||
|
||||
})(window);
|
||||
338
gaia/tv_apps/browser/js/historyStore.js
Executable file
|
|
@ -0,0 +1,338 @@
|
|||
/* globals BrowserDB, SyncBrowserDB, SyncManagerBridge */
|
||||
'use strict';
|
||||
|
||||
(function(exports){
|
||||
|
||||
const INITIAL_HISTORY_COUNT = 21;
|
||||
|
||||
const DAY_IN_MILLI_SECOND = 86400000;
|
||||
|
||||
// TODO: for the live time history data loading task.
|
||||
var HistoryStore = {
|
||||
isSynced: false,
|
||||
|
||||
cache: new Map(),
|
||||
|
||||
cacheIndexStartAt: 0,
|
||||
|
||||
cacheIndexEndAt: 0,
|
||||
|
||||
lastDataIndex: null,
|
||||
|
||||
currentFolder: null,
|
||||
|
||||
isHandlingCache: false,
|
||||
|
||||
cacheEventQueue: [],
|
||||
|
||||
reset: function (cb) {
|
||||
return new Promise(resolve => {
|
||||
this.cache.clear();
|
||||
this.cacheIndexStartAt = 0;
|
||||
this.cacheIndexEndAt = 0;
|
||||
this.lastDataIndex = null;
|
||||
this.currentFolder = null;
|
||||
this.isHandlingCache = false;
|
||||
this.cacheEventQueue = [];
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
resolve();
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
SyncManagerBridge.getInfo().then(message => {
|
||||
if (message.state === 'enabled' || message.state === 'syncing') {
|
||||
this.isSynced = true;
|
||||
} else {
|
||||
this.isSynced = false;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
getByRange: function (start, num, folderId, cb) {
|
||||
var self = this;
|
||||
function getCaches(){
|
||||
|
||||
var length = (start + num) > self.cache.size ?
|
||||
(self.cache.size - start) : (start + num);
|
||||
var result = [];
|
||||
|
||||
for(var i = start; i < length; i++) {
|
||||
result.push(self.cache.get(i));
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
if(folderId !== this.currentFolder) {
|
||||
this.currentFolder = folderId;
|
||||
this.fetchCache().then(() => {
|
||||
cb(getCaches());
|
||||
});
|
||||
} else {
|
||||
cb(getCaches());
|
||||
}
|
||||
},
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
traverseNextCache: function(cacheIndex) {
|
||||
return new Promise(resolve => {
|
||||
var traverse = () =>{
|
||||
var start = this.cache.get(this.cacheIndexStartAt).timestamp;
|
||||
SyncBrowserDB.getHistoryTimestamp(start, 'next', false,
|
||||
timestamp => {
|
||||
if(timestamp) {
|
||||
this.updateCacheByNextHistory(timestamp).then(() => {
|
||||
var history = this.cache.get(cacheIndex);
|
||||
if(!history) {
|
||||
traverse();
|
||||
} else {
|
||||
resolve(history);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
traverse();
|
||||
});
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
traversePreviousCache: function(cacheIndex) {
|
||||
return new Promise(resolve => {
|
||||
var traverse = () => {
|
||||
var start = this.cache.get(this.cacheIndexEndAt).timestamp;
|
||||
SyncBrowserDB.getHistoryTimestamp(start, 'prev', false,
|
||||
timestamp => {
|
||||
if(timestamp) {
|
||||
this.updateCacheByPreviousHistory(timestamp).then(() => {
|
||||
var history = this.cache.get(cacheIndex);
|
||||
if(!history) {
|
||||
traverse();
|
||||
} else {
|
||||
resolve(history);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.lastDataIndex = this.cacheIndexEndAt;
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
traverse();
|
||||
});
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
getCache: function(cacheIndex) {
|
||||
return new Promise(resolve => {
|
||||
var history = null;
|
||||
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
if(this.cacheIndexStartAt <= cacheIndex <= this.cacheIndexEndAt) {
|
||||
resolve(null);
|
||||
} else {
|
||||
history = this.cache.get(cacheIndex);
|
||||
resolve(history);
|
||||
}
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
if(cacheIndex < this.cacheIndexStartAt) {
|
||||
this.traverseNextCache(cacheIndex).then(history => {
|
||||
resolve(history);
|
||||
});
|
||||
} else if (cacheIndex > this.cacheIndexEndAt) {
|
||||
if(this.lastDataIndex && cacheIndex > this.lastDataIndex) {
|
||||
resolve(history);
|
||||
} else {
|
||||
this.traversePreviousCache(cacheIndex).then(history => {
|
||||
resolve(history);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
history = this.cache.get(cacheIndex);
|
||||
resolve(history);
|
||||
}
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
handleCacheEvent: function() {
|
||||
if(!this.isHandlingCache) {
|
||||
this.isHandlingCache = true;
|
||||
var handler = this.cacheEventQueue.shift();
|
||||
handler().then(() => {
|
||||
this.isHandlingCache = false;
|
||||
if(this.cacheEventQueue.length > 0) {
|
||||
this.handleCacheEvent();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getByIndex: function (index, folderId, cb) {
|
||||
var handler = () => new Promise(resolve => {
|
||||
if(folderId !== this.currentFolder) {
|
||||
this.currentFolder = folderId;
|
||||
this.fetchCache().then(() => {
|
||||
this.getCache(index).then(history => {
|
||||
cb(history);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.getCache(index).then(history => {
|
||||
cb(history);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.cacheEventQueue.push(handler);
|
||||
this.handleCacheEvent();
|
||||
},
|
||||
|
||||
fetchCache: function () {
|
||||
this.cache.clear();
|
||||
this.cacheIndexStartAt = 0;
|
||||
this.cacheIndexEndAt = -1;
|
||||
this.lastDataIndex = null;
|
||||
|
||||
return new Promise( resolve => {
|
||||
if(!this.isSynced) {
|
||||
// Get bookmark data from origin indexdDB
|
||||
BrowserDB.getHistory(localHistory => {
|
||||
localHistory.forEach(history => {
|
||||
this.cacheIndexEndAt++;
|
||||
this.cache.set(this.cacheIndexEndAt, history);
|
||||
});
|
||||
this.lastDataIndex = this.cacheIndexEndAt;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
//IFNDEF_FIREFOX_SYNC
|
||||
resolve();
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
if(!this.currentFolder) {
|
||||
// XXX: Ideally we should use Promise.all for
|
||||
// SyncBrowserDB.getBookmark and BrowserDB.getBookmarks.
|
||||
// But now the implementation of these function are not
|
||||
// return Promise.
|
||||
SyncBrowserDB.getHistoryTimestamp(Date.now(), 'prev', true,
|
||||
timestamp => {
|
||||
// We query remote history by parentid for checking if
|
||||
// the indexedDB has remote history data. If there is
|
||||
// any remote data, we create a folder item to show the
|
||||
// synced history.
|
||||
if(timestamp) {
|
||||
this.cacheIndexEndAt++;
|
||||
this.cache.set(this.cacheIndexEndAt, {
|
||||
id: 'sync_history',
|
||||
title: 'Synced History',
|
||||
type: 'folder',
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
// get bookmark data from origin indexdDB
|
||||
BrowserDB.getHistory(localHistory => {
|
||||
localHistory.forEach(history => {
|
||||
this.cacheIndexEndAt++;
|
||||
this.cache.set(this.cacheIndexEndAt, history);
|
||||
});
|
||||
this.lastDataIndex = this.cacheIndexEndAt;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
SyncBrowserDB.getHistory(INITIAL_HISTORY_COUNT, syncHistory => {
|
||||
syncHistory.forEach(history => {
|
||||
// XXX: the data from firefox sync can't be modified.
|
||||
history.readOnly = true;
|
||||
this.cacheIndexEndAt++;
|
||||
this.cache.set(this.cacheIndexEndAt, history);
|
||||
});
|
||||
|
||||
if(syncHistory.length < INITIAL_HISTORY_COUNT) {
|
||||
this.lastDataIndex = this.cacheIndexEndAt;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
updateCacheByNextHistory: function (timestamp) {
|
||||
var promise = new Promise(resolve => {
|
||||
var start = timestamp,
|
||||
end = timestamp + DAY_IN_MILLI_SECOND;
|
||||
|
||||
SyncBrowserDB.getHistoryByTime(start, end, true, true, syncHistory => {
|
||||
this.cache.clear();
|
||||
this.cacheIndexEndAt = this.cacheIndexStartAt - 1;
|
||||
var length = syncHistory.length;
|
||||
while(length--) {
|
||||
this.cacheIndexStartAt--;
|
||||
this.cache.set(this.cacheIndexStartAt, syncHistory[length]);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
updateCacheByPreviousHistory: function (timestamp) {
|
||||
var promise = new Promise(resolve => {
|
||||
var start = timestamp - DAY_IN_MILLI_SECOND,
|
||||
end = timestamp;
|
||||
|
||||
SyncBrowserDB.getHistoryByTime(start, end, true, true, syncHistory => {
|
||||
this.cache.clear();
|
||||
this.cacheIndexStartAt = this.cacheIndexEndAt + 1;
|
||||
syncHistory.forEach(history => {
|
||||
// XXX: the data from firefox sync can't be modified.
|
||||
history.readOnly = true;
|
||||
this.cacheIndexEndAt++;
|
||||
this.cache.set(this.cacheIndexEndAt, history);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
|
||||
removeCache: function (index) {
|
||||
if(this.cacheIndexStartAt <= index <= this.cacheIndexEndAt) {
|
||||
var value = null;
|
||||
for(var i = index; i<this.cacheIndexEndAt; i++) {
|
||||
value = this.cache.get(i+1);
|
||||
this.cache.set(i, value);
|
||||
}
|
||||
this.cache.delete(this.cacheIndexEndAt);
|
||||
this.cacheIndexEndAt--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.HistoryStore = HistoryStore;
|
||||
})(window);
|
||||
1221
gaia/tv_apps/browser/js/index.js
Executable file
136
gaia/tv_apps/browser/js/resource_loader.js
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* HTML files to be loaded should be enclosed by a <template> element.
|
||||
* <template>
|
||||
* put HTML content here
|
||||
* </template>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function (exports) {
|
||||
|
||||
/**
|
||||
* ResourceLoader is a stand-alone module which provides methods for lazy
|
||||
* loading HTML, CSS and javascript resources.
|
||||
*
|
||||
* TODO: Methods for lazy loading javascript files.
|
||||
*
|
||||
* @param {object} options [description]
|
||||
*/
|
||||
function ResourceLoader(options) {
|
||||
this.path = '';
|
||||
|
||||
this.init(options);
|
||||
}
|
||||
|
||||
ResourceLoader.prototype = {
|
||||
|
||||
constructor: ResourceLoader,
|
||||
|
||||
init(options) {
|
||||
options = options || {};
|
||||
|
||||
this.path = options.path || 'elements';
|
||||
},
|
||||
|
||||
/**
|
||||
* Load HTML and CSS by name.
|
||||
* @param {string} name [description]
|
||||
* @return {Promise} The returned Promise is resolved with a Node.
|
||||
*/
|
||||
loadByName(name) {
|
||||
var path = [this.path, name].join('/');
|
||||
this.loadCss(path);
|
||||
return this.loadHtml(path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load an HTML file from the specified path, the resulted DOM Node can be
|
||||
* directly appended or modified with DOM API.
|
||||
* @param {string} path The path of the HTML file, with or without
|
||||
* html extension.
|
||||
* @return {Promise} The returned Promise is resolved with a DOM Node.
|
||||
*/
|
||||
loadHtml(path) {
|
||||
if (!path.endsWith('.html')) {
|
||||
path = [path, 'html'].join('.');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
var template = xhr.responseXML.querySelector('template');
|
||||
var node = document.importNode(template.content, true);
|
||||
resolve(node);
|
||||
};
|
||||
xhr.open('GET', path);
|
||||
xhr.responseType = 'document';
|
||||
xhr.overrideMimeType('text/html');
|
||||
xhr.send();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Load an CSS file from the specified path.
|
||||
* @param {string} path The path of the CSS file, with or without
|
||||
* css extension.
|
||||
* @return {Promise}
|
||||
*/
|
||||
loadCss(path) {
|
||||
if (!path.endsWith('.css')) {
|
||||
path = [path, 'css'].join('.');
|
||||
}
|
||||
|
||||
if (document.querySelector('link[rel=stylesheet][href="' + path + '"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var link = document.createElement('link');
|
||||
link.type = 'text/css';
|
||||
link.rel = 'stylesheet';
|
||||
link.href = path;
|
||||
link.addEventListener('load', () => {
|
||||
resolve();
|
||||
});
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Unload a loaded template. We don't cache HTML templates, so we only need
|
||||
* to remove the loaded link element from document.head.
|
||||
* @param {string} name The name of the template.
|
||||
*/
|
||||
unloadByName(name) {
|
||||
var path = [this.path, name].join('/');
|
||||
this.unloadCss(path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a CSS link element from document.head.
|
||||
* @param {string} path The path of the CSS file, with or without
|
||||
* css extension.
|
||||
*/
|
||||
unloadCss(path) {
|
||||
if (!path.endsWith('.css')) {
|
||||
path = [path, 'css'].join('.');
|
||||
}
|
||||
|
||||
var link =
|
||||
document.querySelector('link[rel=stylesheet][href="' + path + '"]');
|
||||
|
||||
if (link) {
|
||||
document.head.removeChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.ResourceLoader = new ResourceLoader();
|
||||
|
||||
})(window);
|
||||
242
gaia/tv_apps/browser/js/search_result.js
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
/* global _, Browser, BrowserDB, MozActivity */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Browser app search_result
|
||||
* @namespace SearchResult
|
||||
*/
|
||||
var SearchResult = {
|
||||
|
||||
listTemplate: null,
|
||||
searchResultTemplate: null,
|
||||
allResult: null,
|
||||
updateInProgress: false,
|
||||
pendingUpdateFilter: null,
|
||||
|
||||
/**
|
||||
* Intialise SearchResult.
|
||||
*/
|
||||
init: function result_init() {
|
||||
/* get Element */
|
||||
this.getAllElements();
|
||||
|
||||
/* Event Listener */
|
||||
window.addEventListener('click', this);
|
||||
|
||||
// Create template elements
|
||||
this.listTemplate = this.createList();
|
||||
this.searchResultTemplate = this.createSearchTemplate();
|
||||
},
|
||||
|
||||
/**
|
||||
* CamelCase (xxx-yyy -> xxxYyy)
|
||||
*/
|
||||
toCamelCase: function toCamelCase(str) {
|
||||
return str.replace(/-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get All Elements (from id)
|
||||
*/
|
||||
getAllElements: function result_getAllElements() {
|
||||
var elementIDs = [
|
||||
'search-result', 'search-result-list',
|
||||
];
|
||||
|
||||
// Loop and add element with camel style name to Modal Dialog attribute.
|
||||
elementIDs.forEach(function createElementRef(name) {
|
||||
this[this.toCamelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
show: function result_show() {
|
||||
this.searchResult.classList.add('visible');
|
||||
this.handleSearchResultFormInput(null);
|
||||
},
|
||||
|
||||
hide: function result_hide() {
|
||||
var list = this.searchResultList.firstElementChild;
|
||||
if( list ) this.searchResultList.removeChild(list);
|
||||
this.searchResult.classList.remove('visible');
|
||||
Toolbar.setSearchBarStyle('false');
|
||||
},
|
||||
|
||||
isDisplayed: function result_isDisplayed() {
|
||||
return this.searchResult.classList.contains('visible');
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit search form
|
||||
*/
|
||||
handleSearchResultFormInput: function result_handleSearchResultFormInput(ev) {
|
||||
if( ev ) ev.preventDefault();
|
||||
this.resultUpdate(Toolbar.searchInput.value);
|
||||
},
|
||||
|
||||
handleSearchResultFormSubmit: function result_handleSearchResultFormSubmit(ev) {
|
||||
if( ev ) ev.preventDefault();
|
||||
},
|
||||
|
||||
createList: function result_createList() {
|
||||
var list = document.createElement('ul');
|
||||
list.classList.add('result-box');
|
||||
return list;
|
||||
},
|
||||
|
||||
createSearchTemplate: function result_createSearchTemplate() {
|
||||
var template = document.createElement('li');
|
||||
template.classList.add('result-item');
|
||||
var link = document.createElement('div');
|
||||
link.classList.add('result-base');
|
||||
var title = document.createElement('div');
|
||||
title.classList.add('result-title');
|
||||
var url = document.createElement('div');
|
||||
url.classList.add('result-url');
|
||||
|
||||
link.appendChild(title);
|
||||
link.appendChild(url);
|
||||
template.appendChild(link);
|
||||
|
||||
return template;
|
||||
},
|
||||
|
||||
resultUpdate: function result_resultUpdate(filter) {
|
||||
// If an update is already in progress enqueue the following ones
|
||||
if (this.updateInProgress) {
|
||||
this.pendingUpdateFilter = filter;
|
||||
return;
|
||||
} else {
|
||||
this.updateInProgress = true;
|
||||
}
|
||||
|
||||
this.allResult = [];
|
||||
BrowserDB.getBookmarks((function(bookmarks) {
|
||||
this.resultFiltering(filter, bookmarks);
|
||||
BrowserDB.getHistory((function(history) {
|
||||
this.resultFiltering(filter, history);
|
||||
this.populateResults(filter);
|
||||
|
||||
this.updateInProgress = false;
|
||||
var pendingUpdateFilter = this.pendingUpdateFilter;
|
||||
if (pendingUpdateFilter !== null) {
|
||||
this.pendingUpdateFilter = null;
|
||||
this.resultUpdate(pendingUpdateFilter);
|
||||
}
|
||||
}).bind(this));
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
resultFiltering: function result_resultFiltering(filter, data) {
|
||||
if(( filter == '' ) || ( data == null )) return;
|
||||
|
||||
for( var i = 0 ; i < data.length ; i ++ ) {
|
||||
var current = data[i];
|
||||
var matched = false;
|
||||
if( filter ) {
|
||||
matched = this.matchesFilter(current.uri, filter) ||
|
||||
this.matchesFilter(current.title, filter);
|
||||
}
|
||||
if( matched || !filter ) {
|
||||
var j = 0;
|
||||
for( ; j < this.allResult.length ; j ++ ) {
|
||||
if( this.allResult[j].uri === current.uri ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( j == this.allResult.length ) {
|
||||
this.allResult.push(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the URI matches the regular expression filter.
|
||||
* @param {String} uri
|
||||
* @param {String} filter
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
matchesFilter: function result_matchesFilter(uri, filter) {
|
||||
if( !uri ) return false;
|
||||
return uri.match(new RegExp(filter, 'i')) !== null;
|
||||
},
|
||||
|
||||
populateResults: function result_populateResults(filter) {
|
||||
var list = this.createList();
|
||||
var engine = SearchUtil.getCurrentEngineName();
|
||||
if( engine ) {
|
||||
var result = this.searchResultTemplate.cloneNode(true);
|
||||
var title = result.childNodes[0].childNodes[0];
|
||||
// title.innerHTML = 'Search by ' + engine;
|
||||
navigator.mozL10n.setAttributes(title, 'WB_LT_TIPS_S_SEARCH', {
|
||||
value0:engine
|
||||
});
|
||||
var uri = result.childNodes[0].childNodes[1];
|
||||
uri.innerHTML = Browser.getSearchFromInput(filter);
|
||||
list.appendChild(result);
|
||||
result.addEventListener('mouseup',
|
||||
this.handleResultSelect.bind(this));
|
||||
}
|
||||
|
||||
for( var i = 0 ; i < this.allResult.length ; i ++ ) {
|
||||
var result = this.searchResultTemplate.cloneNode(true);
|
||||
var title = result.childNodes[0].childNodes[0];
|
||||
title.innerHTML = this.allResult[i].title;
|
||||
var uri = result.childNodes[0].childNodes[1];
|
||||
uri.innerHTML = this.allResult[i].uri;
|
||||
list.appendChild(result);
|
||||
result.addEventListener('mouseup',
|
||||
this.handleResultSelect.bind(this));
|
||||
}
|
||||
var oldList = this.searchResultList.firstElementChild;
|
||||
|
||||
if (oldList) {
|
||||
this.searchResultList.replaceChild(list, oldList);
|
||||
} else {
|
||||
this.searchResultList.appendChild(list);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* handle event
|
||||
*/
|
||||
handleEvent: function result_handleEvent(ev) {
|
||||
switch(ev.type) {
|
||||
case 'click':
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleResultSelect: function result_handleResultSelect(ev) {
|
||||
if( !ev ) return;
|
||||
ev.preventDefault();
|
||||
if(( ev.target.lastChild.lastChild.textContent != '' ) ||
|
||||
( ev.target.lastChild.lastChild.textContent != undefined )) {
|
||||
var url = ev.target.lastChild.lastChild.textContent;
|
||||
Browser.navigate(url);
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle Key Event.
|
||||
*/
|
||||
handleKeyEvent: function result_handleKeyEvent(ev) {
|
||||
// in the input area focus (= display keyboard)
|
||||
if(document.activeElement.nodeName == 'INPUT') {
|
||||
return;
|
||||
}
|
||||
switch( ev.keyCode ) {
|
||||
case KeyEvent.DOM_VK_BACK_SPACE :
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
283
gaia/tv_apps/browser/js/search_util.js
Executable file
|
|
@ -0,0 +1,283 @@
|
|||
'use strict';
|
||||
|
||||
var SearchUtil = {
|
||||
// country code
|
||||
COUNTRY: 'US',
|
||||
|
||||
/* search engine list of the search bar */
|
||||
ENGINE_LIST: {
|
||||
'CN': [
|
||||
{id: 2, name: 'Baidu'},
|
||||
{id: 1, name: 'Bing'}
|
||||
],
|
||||
// default
|
||||
'US': [
|
||||
{id: 1, name: 'Bing'},
|
||||
{id: 0, name: 'Google'}
|
||||
]
|
||||
},
|
||||
// search engine
|
||||
engineList: null,
|
||||
searchId: null,
|
||||
searchIdx: null,
|
||||
|
||||
DOMAIN_LIST: {
|
||||
'US':'com' , 'CA':'ca' , 'DE':'de' , 'IE':'ie' ,
|
||||
'NL':'nl' , 'HU':'hu' , 'PL':'pl' , 'CZ':'cz' ,
|
||||
'SK':'sk' , 'IT':'it' , 'GB':'co.uk' , 'SI':'si' ,
|
||||
'GR':'gr' , 'PT':'pt' , 'DK':'dk' , 'FI':'fi' ,
|
||||
'HR':'hr' , 'NO':'no' , 'RO':'ro' , 'RU':'ru' ,
|
||||
'BG':'bg' , 'LT':'lt' , 'YU':'rs' , 'SE':'se' ,
|
||||
'LV':'lv' , 'EE':'ee' , 'TR':'com.tr' , 'BR':'com.br' ,
|
||||
'ID':'co.id' , 'CN':'com.hk' , 'TW':'com.tw' , 'HK':'com.hk' ,
|
||||
'VN':'com.vn' , 'TH':'co.th' , 'SA':'com.sa' , 'FR':'fr' ,
|
||||
'ES':'es' , 'SG':'com.sg' , 'AU':'com.au' , 'NZ':'co.nz' ,
|
||||
'IN':'co.in' , 'MY':'com.my' , 'AE':'ae' , 'KW':'com.kw' ,
|
||||
'MX':'com.mx' , 'AT':'at' , 'BE':'be' , 'CH':'ch' ,
|
||||
'MT':'com.mt' , 'AD':':' , 'LU':'lu' , 'IS':'is' ,
|
||||
'UA':'com.ua' , 'KZ':'kz' , 'CIS':'by' , 'UK':'co.uk' ,
|
||||
'CL':'cl' , 'PE':'com.pe' , 'CO':'com.co' , 'AR':'com.ar' ,
|
||||
'PA':'com.pa' , 'JP':'co.jp' , 'MK':'mk' , 'MM':'com.mm'
|
||||
},
|
||||
// search domain
|
||||
domainList: null,
|
||||
|
||||
/* URL of each search engine */
|
||||
SEARCH_URL: {
|
||||
//Google :
|
||||
0 : [
|
||||
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&q=",
|
||||
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=isch&q=",
|
||||
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=vid&q=",
|
||||
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=nws&q=",
|
||||
"http://maps.google.com/maps?q=",
|
||||
"http://www.youtube.DOMAIN/results?search_query="
|
||||
],
|
||||
//bing :
|
||||
1 : [
|
||||
"http://www.bing.com/search?FORM=PANSSB&PC=PANS&q=",
|
||||
"http://www.bing.com/images/search?FORM=PANSSB&PC=PANS&q=",
|
||||
//"http://www.bing.com/videos/search?FORM=PANSSB&PC=PANS&q=",
|
||||
"http://www.bing.com/?scope=video&FORM=PANSSB&PC=PANS&q=",
|
||||
"http://www.bing.com/news/search?FORM=PANSSB&PC=PANS&q=",
|
||||
"http://www.bing.com/maps/?FORM=PANSSB&PC=PANS&q=",
|
||||
"http://www.bing.com/videos/search?FORM=PANSSB&PC=PANS&q="
|
||||
],
|
||||
//Baidu :
|
||||
2 : [
|
||||
"http://www.baidu.com/s?word=",
|
||||
"http://image.baidu.com/i?word=",
|
||||
"http://video.baidu.com/v?word=",
|
||||
"http://news.baidu.com/ns?word=",
|
||||
"http://map.baidu.com/m?word=",
|
||||
"http://video.baidu.com/v?word="
|
||||
]
|
||||
},
|
||||
searchUrl: null,
|
||||
|
||||
// keyword for auto category selection at voice search
|
||||
SEARCH_CAT: {
|
||||
//Google :
|
||||
0 :
|
||||
[
|
||||
[ [ "www.google.", "/search", "" ] ],
|
||||
[ [ "www.google.", "/search", "tbm=isch" ],
|
||||
[ "www.google.", "/imghp", "" ] ],
|
||||
[ [ "www.google.", "/search", "tbm=vid" ],
|
||||
[ "www.google.", "/videohp","" ] ],
|
||||
[ [ "www.google.", "/search", "tbm=nws" ],
|
||||
[ "news.google.", "/nwshp", "" ] ],
|
||||
[ [ "maps.google.", "", "" ],
|
||||
[ "www.google.", "/maps", "" ] ],
|
||||
[ [ "www.youtube.", "", "" ] ]
|
||||
],
|
||||
//Bing :
|
||||
1 :
|
||||
[
|
||||
[ [ "www.bing.com", "/search", "" ] ],
|
||||
[ [ "www.bing.com", "/images", "" ],
|
||||
[ "www.bing.com", "", "scope=images" ] ],
|
||||
[ [ "www.bing.com", "/videos", "" ],
|
||||
[ "www.bing.com", "", "scope=video" ] ],
|
||||
[ [ "www.bing.com", "/news", "" ],
|
||||
[ "www.bing.com", "", "scope=news" ] ],
|
||||
[ [ "www.bing.com", "/maps", "" ] ],
|
||||
[ [ "www.bing.com", "/videos", "" ],
|
||||
[ "www.bing.com", "", "scope=video" ] ]
|
||||
],
|
||||
//Baidu :
|
||||
2 :
|
||||
[
|
||||
[ [ "www.baidu.com", "", "" ] ],
|
||||
[ [ "image.baidu.com", "", "" ] ],
|
||||
[ [ "video.baidu.com", "", "" ] ],
|
||||
[ [ "news.baidu.com", "", "" ] ],
|
||||
[ [ "map.baidu.com", "", "" ] ],
|
||||
[ [ "video.baidu.com", "", "" ] ]
|
||||
]
|
||||
},
|
||||
|
||||
displaySearchEngineName: false,
|
||||
|
||||
/**
|
||||
* init SearchUtil
|
||||
*/
|
||||
init: function searchUtil_init(country, cb) {
|
||||
// set country code
|
||||
this.COUNTRY = country;
|
||||
|
||||
// search engine (default US)
|
||||
this.engineList = (country in this.ENGINE_LIST)?
|
||||
this.ENGINE_LIST[country]: this.ENGINE_LIST['US'];
|
||||
|
||||
// search domain (default US)
|
||||
this.domainList = (country in this.DOMAIN_LIST)?
|
||||
this.DOMAIN_LIST[country]: this.DOMAIN_LIST['US'];
|
||||
|
||||
// search URL (default US)
|
||||
this.searchUrl = this.SEARCH_URL;
|
||||
var index;
|
||||
for(index in this.searchUrl[0]){
|
||||
this.searchUrl[0][index] =
|
||||
this.searchUrl[0][index].replace(/DOMAIN/, this.domainList);
|
||||
}
|
||||
|
||||
// get all database
|
||||
this.getAllDatabase(cb);
|
||||
},
|
||||
|
||||
/**
|
||||
* get all database
|
||||
*/
|
||||
getAllDatabase: function searchUtil_getAllDatabase(cb){
|
||||
// DB get all
|
||||
this.getSearchEngineId((function(){
|
||||
this.initDatabase((function(){
|
||||
if(cb){
|
||||
cb();
|
||||
}
|
||||
}).bind(this))
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
// init database
|
||||
initDatabase: function search_initDatabase(cb){
|
||||
// default search engine url
|
||||
if(this.searchId != null){
|
||||
this.convSearchIdToIdx(this.searchId);
|
||||
}else{
|
||||
this.searchIdx = 0;
|
||||
this.displaySearchEngineName = true;
|
||||
}
|
||||
if(cb){
|
||||
cb();
|
||||
}
|
||||
},
|
||||
|
||||
// get search engine id
|
||||
getSearchEngineId: function searchUtil_getSearchEngineId(cb){
|
||||
BrowserDB.getSetting('SEARCH_ENGINE', (function(result) {
|
||||
Browser.debug("SEARCH_ENGINE = " + result);
|
||||
if(result != undefined){
|
||||
this.searchId = result;
|
||||
this.convSearchIdToIdx(result);
|
||||
}
|
||||
if(cb){
|
||||
cb(result);
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
// set search engine id
|
||||
setSearchEngineId: function searchUtil_setSearchEngineId(id, cb){
|
||||
this.searchId = id;
|
||||
this.convSearchIdToIdx(id);
|
||||
BrowserDB.updateSetting(id, 'SEARCH_ENGINE', cb);
|
||||
},
|
||||
|
||||
// get search engine index
|
||||
getSearchEngineIdx: function searchUtil_getSearchEngineIdx(cb){
|
||||
if(this.searchId){
|
||||
this.convSearchIdToIdx(this.searchId);
|
||||
if(cb){
|
||||
cb();
|
||||
}
|
||||
}else{
|
||||
BrowserDB.getSetting('SEARCH_ENGINE', (function(result) {
|
||||
if(result != undefined){
|
||||
this.convSearchIdToIdx(result);
|
||||
}
|
||||
if(cb){
|
||||
cb();
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
// Convert the search_id (DB) to search index.
|
||||
convSearchIdToIdx: function searchUtil_convSearchIdToIdx(search_id) {
|
||||
var index = null;
|
||||
for(var i in this.engineList){
|
||||
if(search_id == this.engineList[i].id){
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index == null){
|
||||
// terminated search service of Web. so select default.
|
||||
this.searchIdx = 0;
|
||||
this.searchId = this.engineList[0].id;
|
||||
BrowserDB.updateSetting(undefined, 'SEARCH_ENGINE');
|
||||
this.displaySearchEngineName = false;
|
||||
}else{
|
||||
this.searchIdx = index;
|
||||
this.displaySearchEngineName = true;
|
||||
}
|
||||
},
|
||||
|
||||
// Get Search Engine List.
|
||||
getEngineList: function searchUtil_getEngineList(){
|
||||
return this.engineList;
|
||||
},
|
||||
// Get Current Search Engine List.
|
||||
getCurrentEngineList: function searchUtil_getCurrentEngineList(){
|
||||
if(this.searchIdx != null && this.searchIdx < this.engineList.length) {
|
||||
return this.engineList[this.searchIdx];
|
||||
}else{
|
||||
return this.engineList[0];
|
||||
}
|
||||
},
|
||||
// Get Current Search Engine Name.
|
||||
getCurrentEngineName: function searchUtil_getCurrentEngineName(){
|
||||
if(this.searchIdx != null && this.searchIdx < this.engineList.length) {
|
||||
return this.engineList[this.searchIdx].name;
|
||||
}else{
|
||||
return this.engineList[0].name;
|
||||
}
|
||||
},
|
||||
// Get Search Id.
|
||||
getSearchId: function searchUtil_getSearchId(){
|
||||
return this.searchId;
|
||||
},
|
||||
// Get Search Index.
|
||||
getSearchIdx: function searchUtil_getSearchIdx(){
|
||||
return this.searchIdx;
|
||||
},
|
||||
// Get Search Url.
|
||||
getSearchUrl: function searchUtil_getSearchUrl(){
|
||||
return this.searchUrl;
|
||||
},
|
||||
// Get Current Search Url.
|
||||
getCurrentSearchUrl: function searchUtil_getCurrentSearchUrl(){
|
||||
if(this.searchId != null && (this.searchId in this.searchUrl)){
|
||||
return this.searchUrl[this.searchId];
|
||||
}else{
|
||||
return this.searchUrl[this.engineList[0].id];
|
||||
}
|
||||
},
|
||||
// Get Display Search Engine Name flag.
|
||||
getDispFlag: function searchUtil_getDispFlag(){
|
||||
return this.displaySearchEngineName;
|
||||
},
|
||||
};
|
||||
|
||||
719
gaia/tv_apps/browser/js/settings.js
Executable file
|
|
@ -0,0 +1,719 @@
|
|||
/* exported Settings */
|
||||
/* global Awesomescreen */
|
||||
/* global Browser */
|
||||
/* global BrowserDB */
|
||||
/* global BrowserDialog */
|
||||
/* global KeyEvent */
|
||||
/* global LazyLoader */
|
||||
/* global SearchUtil */
|
||||
/* global Toolbar */
|
||||
/* global UrlHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Browser app settings panel.
|
||||
* @namespace Settings
|
||||
*/
|
||||
var Settings = {
|
||||
DEFAULT_HOMEPAGE: 'http://www.mozilla.org',
|
||||
currentSearchEngine: null,
|
||||
selectSearchEngine: null,
|
||||
searchEngineList: null,
|
||||
focusList: null,
|
||||
focusPos: 0,
|
||||
|
||||
/** Get all DOM elements when inited. */
|
||||
getAllElements: function settings_getAllElements() {
|
||||
|
||||
var toCamelCase = function toCamelCase(str) {
|
||||
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
var elementIDs = [
|
||||
'settings-list', 'settings-list-area',
|
||||
'settings-homepage', 'settings-homepage-name',
|
||||
'settings-search-engine', 'settings-search-engine-name',
|
||||
'settings-clear-history',
|
||||
'settings-clear-cookie',
|
||||
|
||||
'settings-dialog',
|
||||
|
||||
'settings-dialog-homepage',
|
||||
'settings-dialog-homepage-input',
|
||||
'settings-dialog-homepage-input-area',
|
||||
'settings-dialog-homepage-clear',
|
||||
'settings-dialog-homepage-default',
|
||||
'settings-dialog-homepage-cancel',
|
||||
'settings-dialog-homepage-ok',
|
||||
|
||||
'settings-dialog-search',
|
||||
'settings-dialog-search-select',
|
||||
'settings-dialog-search-select-list',
|
||||
'settings-dialog-search-cancel',
|
||||
'settings-dialog-search-ok',
|
||||
|
||||
'settings-banner-message'
|
||||
];
|
||||
|
||||
// Loop and add element with camel style name to Modal Dialog attribute.
|
||||
elementIDs.forEach(function createElementRef(name) {
|
||||
this[toCamelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Intialise settings panel.
|
||||
*/
|
||||
init: function settings_init() {
|
||||
this.getAllElements();
|
||||
|
||||
this.getDefaultHomepage((function(result) {
|
||||
/* default homepage nothing... */
|
||||
Browser.debug('default homepage = ' + result);
|
||||
if(!result) {
|
||||
console.log('set default homepage...');
|
||||
this.setDefaultHomepage();
|
||||
this.setHomepage(this.DEFAULT_HOMEPAGE);
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
this.settingsList.addEventListener('mouseup',
|
||||
this.handleListClick.bind(this));
|
||||
this.settingsListArea.addEventListener('mouseup',
|
||||
this.handleListAreaClick.bind(this));
|
||||
this.settingsDialog.addEventListener('mouseup',
|
||||
this.handleListAreaClick.bind(this));
|
||||
|
||||
this.settingsHomepage.addEventListener('mouseup',
|
||||
this.handleHomepageClick.bind(this));
|
||||
this.settingsSearchEngine.addEventListener('mouseup',
|
||||
this.handleSearchEngineClick.bind(this));
|
||||
this.settingsClearHistory.addEventListener('mouseup',
|
||||
this.handleClearHistoryClick.bind(this));
|
||||
this.settingsClearCookie.addEventListener('mouseup',
|
||||
this.handleClearCookieClick.bind(this));
|
||||
|
||||
this.settingsDialogHomepageInput.addEventListener('submit',
|
||||
this.handleDialogHomepageInputSubmit.bind(this));
|
||||
this.settingsDialogHomepageClear.addEventListener('mouseup',
|
||||
this.handleDialogHomepageClear.bind(this));
|
||||
this.settingsDialogHomepageDefault.addEventListener('mouseup',
|
||||
this.handleDialogHomepageDefault.bind(this));
|
||||
this.settingsDialogHomepageCancel.addEventListener('mouseup',
|
||||
this.handleDialogHomepageCancel.bind(this));
|
||||
this.settingsDialogHomepageOk.addEventListener('mouseup',
|
||||
this.handleDialogHomepageOk.bind(this));
|
||||
|
||||
this.settingsDialogSearchCancel.addEventListener('mouseup',
|
||||
this.handleDialogSearchCancel.bind(this));
|
||||
this.settingsDialogSearchOk.addEventListener('mouseup',
|
||||
this.handleDialogSearchOk.bind(this));
|
||||
|
||||
this.settingsDialogHomepageInputArea.addEventListener('focus',
|
||||
this.handleDialogHomepageInputAreaFocus.bind(this));
|
||||
this.settingsDialogHomepageInputArea.addEventListener('mousedown',
|
||||
this.handleDialogHomepageInputAreaClick.bind(this));
|
||||
|
||||
this.settingsDialogHomepageInputArea.addEventListener('blur',
|
||||
this.handleDialogHomepageInputBlur.bind(this));
|
||||
this.settingsDialogHomepageClear.addEventListener('keyup',
|
||||
this.handleDialogHomepageClear.bind(this));
|
||||
this.settingsDialogHomepageDefault.addEventListener('keyup',
|
||||
this.handleDialogHomepageDefault.bind(this));
|
||||
this.settingsDialogHomepageCancel.addEventListener('keyup',
|
||||
this.handleDialogHomepageCancel.bind(this));
|
||||
this.settingsDialogHomepageOk.addEventListener('keyup',
|
||||
this.handleDialogHomepageOk.bind(this));
|
||||
|
||||
this.settingsDialogSearchCancel.addEventListener('keyup',
|
||||
this.handleDialogSearchCancel.bind(this));
|
||||
this.settingsDialogSearchOk.addEventListener('keyup',
|
||||
this.handleDialogSearchOk.bind(this));
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
LazyLoader.load('js/sync/settings.js');
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
},
|
||||
|
||||
getDefaultHomepage: function settings_getDefaultHomepage(cb) {
|
||||
BrowserDB.db.open((function() {
|
||||
BrowserDB.getSetting( 'default_homepage', cb );
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
setDefaultHomepage: function settings_setDefaultHomepage() {
|
||||
BrowserDB.db.open((function() {
|
||||
BrowserDB.updateSetting( this.DEFAULT_HOMEPAGE, 'default_homepage' );
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
getHomepage: function settings_getHomepage(cb) {
|
||||
BrowserDB.db.open((function() {
|
||||
BrowserDB.getSetting( 'settings_homepage', cb );
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
setHomepage: function settings_setHomepage(url) {
|
||||
BrowserDB.db.open((function() {
|
||||
BrowserDB.updateSetting( url, 'settings_homepage' );
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Show settings panel.
|
||||
*/
|
||||
show: function settings_show() {
|
||||
document.body.classList.add('settings-screen');
|
||||
this.getHomepage((function(result) {
|
||||
this.settingsHomepageName.textContent = result;
|
||||
}).bind(this));
|
||||
this.settingsSearchEngineName.textContent =
|
||||
SearchUtil.getCurrentEngineName();
|
||||
this.currentSearchEngine = this.settingsSearchEngineName.textContent;
|
||||
this.selectSearchEngine = this.currentSearchEngine;
|
||||
|
||||
// XXX: Get Firefox Account sign in status and display it
|
||||
},
|
||||
|
||||
/**
|
||||
* settings list displays a confirmation.
|
||||
*/
|
||||
isDisplayed: function settings_isDisplayed() {
|
||||
return document.body.classList.contains('settings-screen');
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide settings panel.
|
||||
*/
|
||||
hide: function settings_hide() {
|
||||
// Browser.switchCursorMode( true );
|
||||
BrowserDialog.cancelDialog();
|
||||
this.hideDialogHomepage();
|
||||
this.hideDialogSearch();
|
||||
document.body.classList.remove('settings-screen');
|
||||
},
|
||||
|
||||
handleListClick: function settings_handleListClick(evt) {
|
||||
if( evt ) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
|
||||
handleListAreaClick: function settings_handleListAreaClick(evt) {
|
||||
if( evt ) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
focusChange: function settings_focusChange(pos) {
|
||||
var opt = null;
|
||||
var flg = false;
|
||||
if( Awesomescreen.pointerImg.style.display == 'none' ) {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
flg = true;
|
||||
}
|
||||
for( var i = 0 ; i < Settings.focusList.length ; i ++ ) {
|
||||
if( i == pos ) {
|
||||
if(Settings.focusList[i].nodeName == 'INPUT') {
|
||||
if( !Settings.settingsDialogHomepageInput
|
||||
.classList.contains('exfocus') ) {
|
||||
Settings.settingsDialogHomepageInput.classList.add('exfocus');
|
||||
}
|
||||
} else if(Settings.focusList[i].id == 'settings-dialog-homepage-clear'){
|
||||
Settings.focusList[i].focus();
|
||||
if( !Settings.settingsDialogHomepageInput
|
||||
.classList.contains('exfocus') ) {
|
||||
Settings.settingsDialogHomepageInput.classList.add('exfocus');
|
||||
}
|
||||
if( flg ) {
|
||||
opt = Awesomescreen.DEFAULT_EXCLEAR;
|
||||
}
|
||||
} else {
|
||||
Settings.focusList[i].focus();
|
||||
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
|
||||
}
|
||||
Awesomescreen.focusImgFunc(Settings.focusList[i], opt);
|
||||
} else {
|
||||
Settings.focusList[i].blur();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle homepage click.
|
||||
*/
|
||||
handleHomepageClick: function settings_handleHomepageClick() {
|
||||
if( !this.isDialogHomepageDisplayed() ) {
|
||||
this.focusList = [];
|
||||
Browser.switchCursorMode(false);
|
||||
this.settingsDialog.classList.remove('hidden');
|
||||
this.settingsDialogHomepage.classList.remove('hidden');
|
||||
this.settingsDialogHomepageInput.classList.remove('input');
|
||||
this.settingsDialogHomepageInput.classList.remove('exfocus');
|
||||
this.settingsDialogHomepageInputArea.value =
|
||||
this.settingsHomepageName.textContent;
|
||||
this.settingsDialogHomepageInputArea.tabIndex = '0';
|
||||
this.settingsDialogHomepageClear.tabIndex = '0';
|
||||
|
||||
this.focusList.push(this.settingsDialogHomepageInputArea);
|
||||
this.focusList.push(this.settingsDialogHomepageClear);
|
||||
this.focusList.push(this.settingsDialogHomepageDefault);
|
||||
this.focusList.push(this.settingsDialogHomepageOk);
|
||||
this.focusList.push(this.settingsDialogHomepageCancel);
|
||||
this.focusPos = this.focusList.length - 1;
|
||||
this.focusChange(this.focusPos);
|
||||
}
|
||||
},
|
||||
|
||||
hideDialogHomepage: function settings_hideDialogHomepage() {
|
||||
if( this.isDialogHomepageDisplayed() ) {
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
this.settingsDialogHomepageInput.classList.remove('input');
|
||||
this.settingsDialogHomepageInput.classList.remove('exfocus');
|
||||
this.settingsDialogHomepage.classList.add('hidden');
|
||||
this.settingsDialog.classList.add('hidden');
|
||||
Browser.switchCursorMode(true);
|
||||
}
|
||||
},
|
||||
|
||||
isDialogHomepageDisplayed: function settings_isDialogHomepageDisplayed() {
|
||||
return !this.settingsDialogHomepage.classList.contains('hidden');
|
||||
},
|
||||
|
||||
handleDialogHomepageInputSubmit:
|
||||
function settings_handleDialogHomepageInputSubmit(ev) {
|
||||
if (ev) {
|
||||
// Can be canceled
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
var hasScheme =
|
||||
UrlHelper.hasScheme(this.settingsDialogHomepageInputArea.value);
|
||||
// No scheme, prepend basic protocol and return
|
||||
if (!hasScheme) {
|
||||
this.settingsDialogHomepageInputArea.value =
|
||||
'http://' + this.settingsDialogHomepageInputArea.value;
|
||||
}
|
||||
|
||||
this.settingsDialogHomepageInputArea.blur();
|
||||
},
|
||||
|
||||
handleDialogHomepageInputAreaFocus:
|
||||
function settings_handleDialogHomepageInputAreaFocus(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
Settings.settingsDialogHomepageInput.classList.add('input');
|
||||
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
|
||||
},
|
||||
|
||||
handleDialogHomepageInputAreaClick:
|
||||
function settings_handleDialogHomepageInputAreaClick(evt) {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
|
||||
Settings.settingsDialogHomepageInput.classList.add('input');
|
||||
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
|
||||
|
||||
Settings.focusPos = 0;
|
||||
Settings.focusList[Settings.focusPos].focus();
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
},
|
||||
|
||||
handleDialogHomepageInputBlur:
|
||||
function settings_handleDialogHomepageInputBlur(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if( Awesomescreen.pointerImg.style.display == 'none' ) {
|
||||
Browser.switchCursorMode(true);
|
||||
Browser.switchCursorMode(false);
|
||||
}
|
||||
// Pointer image to the input area(fucusPos=0)
|
||||
Settings.focusPos = 0;
|
||||
Settings.settingsDialogHomepageInput.classList.remove('input');
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
Awesomescreen.pointerImg.style.display = 'block';
|
||||
},
|
||||
|
||||
handleDialogHomepageClear:
|
||||
function settings_handleDialogHomepageClear(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
if( evt.keyCode != KeyEvent.DOM_VK_RETURN ) {
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
document.activeElement.blur();
|
||||
document.activeElement.classList.remove('exfocus');
|
||||
}
|
||||
document.activeElement.classList.remove('active');
|
||||
this.settingsDialogHomepageInputArea.value = '';
|
||||
},
|
||||
|
||||
handleDialogHomepageDefault:
|
||||
function settings_handleDialogHomepageDefault(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
if( evt.keyCode != KeyEvent.DOM_VK_RETURN ) {
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
document.activeElement.blur();
|
||||
document.activeElement.classList.remove('exfocus');
|
||||
}
|
||||
document.activeElement.classList.remove('active');
|
||||
this.getDefaultHomepage((function(result) {
|
||||
this.settingsDialogHomepageInputArea.value = result;
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
handleDialogHomepageCancel:
|
||||
function settings_handleDialogHomepageCancel(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
// animation end event handler
|
||||
var end_event = (function() {
|
||||
Settings.settingsDialogHomepageCancel.removeEventListener('transitionend',
|
||||
end_event, false);
|
||||
Settings.hideDialogHomepage();
|
||||
});
|
||||
// end animantion event
|
||||
Settings.settingsDialogHomepageCancel.addEventListener('transitionend',
|
||||
end_event, false);
|
||||
document.activeElement.classList.remove('active');
|
||||
},
|
||||
|
||||
handleDialogHomepageOk: function settings_handleDialogHomepageOk(evt) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
// animation end event handler
|
||||
var end_event = (function() {
|
||||
Settings.settingsDialogHomepageOk.removeEventListener('transitionend',
|
||||
end_event, false);
|
||||
Settings.hideDialogHomepage();
|
||||
});
|
||||
// end animantion event
|
||||
Settings.settingsDialogHomepageOk.addEventListener('transitionend',
|
||||
end_event, false);
|
||||
document.activeElement.classList.remove('active');
|
||||
this.setHomepage(this.settingsDialogHomepageInputArea.value);
|
||||
this.settingsHomepageName.textContent =
|
||||
this.settingsDialogHomepageInputArea.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle search engine click.
|
||||
*/
|
||||
handleSearchEngineClick: function settings_handleSearchEngineClick() {
|
||||
if( !this.isDialogSearchDisplayed() ) {
|
||||
this.focusList = [];
|
||||
Browser.switchCursorMode(false);
|
||||
this.settingsDialog.classList.remove('hidden');
|
||||
this.settingsDialogSearch.classList.remove('hidden');
|
||||
|
||||
this.selectSearchEngine = this.currentSearchEngine;
|
||||
this.searchEngineList = SearchUtil.getEngineList();
|
||||
for(var i in this.searchEngineList) {
|
||||
var title = document.createElement('div');
|
||||
title.classList.add('title');
|
||||
var name = document.createElement('p');
|
||||
name.classList.add('name');
|
||||
name.textContent = this.searchEngineList[i].name;
|
||||
title.appendChild(name);
|
||||
|
||||
var check = document.createElement('div');
|
||||
check.classList.add('check');
|
||||
var onoff = document.createElement('p');
|
||||
if( name.textContent == this.currentSearchEngine ) {
|
||||
name.dataset.display = 'selected';
|
||||
onoff.classList.add('on');
|
||||
} else {
|
||||
name.dataset.display = '';
|
||||
onoff.classList.add('off');
|
||||
}
|
||||
check.appendChild(onoff);
|
||||
|
||||
var item = document.createElement('div');
|
||||
item.classList.add('item');
|
||||
item.appendChild(title);
|
||||
item.appendChild(check);
|
||||
item.tabIndex = '0';
|
||||
this.settingsDialogSearchSelectList.appendChild(item);
|
||||
item.addEventListener('mouseup',
|
||||
this.handleDialogSearchSelected.bind(this));
|
||||
item.addEventListener('keyup',
|
||||
this.handleDialogSearchSelected.bind(this));
|
||||
this.focusList.push(item);
|
||||
}
|
||||
this.focusList.push(this.settingsDialogSearchOk);
|
||||
this.focusList.push(this.settingsDialogSearchCancel);
|
||||
this.focusPos = this.focusList.length - 1;
|
||||
this.focusChange(this.focusPos);
|
||||
}
|
||||
},
|
||||
|
||||
handleClearHistoryClick: function settings_handleClearHistoryClick() {
|
||||
BrowserDialog.createDialog('clear_history', null);
|
||||
},
|
||||
|
||||
handleClearCookieClick: function settings_handleClearCookieClick() {
|
||||
BrowserDialog.createDialog('del_cookie', null);
|
||||
},
|
||||
|
||||
clearCookieFailed: function settings_clearCookieFailed() {
|
||||
// this.settingsBannerMessage.innerHTML =
|
||||
// 'Failed to clear cookies and stored data';
|
||||
this.settingsBannerMessage.setAttribute('data-l10n-id', 'LT_FAILED_');
|
||||
this.showBannerMessage();
|
||||
},
|
||||
|
||||
showBannerMessage: function settings_showBannerMessage() {
|
||||
if( this.settingsBannerMessage.dataset.display != 'visible' ) {
|
||||
this.settingsBannerMessage.dataset.display = 'visible';
|
||||
}
|
||||
clearTimeout( this.showBannerMessageTimeoutID );
|
||||
this.showBannerMessageTimeoutID = setTimeout( function() {
|
||||
Settings.settingsBannerMessage.dataset.display = '';
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
hideDialogSearch: function settings_hideDialogSearch() {
|
||||
if( this.isDialogSearchDisplayed() ) {
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
var childs = this.settingsDialogSearchSelectList.childNodes;
|
||||
var len = childs.length;
|
||||
for(var i = 0 ; i < len ; i ++) {
|
||||
this.settingsDialogSearchSelectList.removeChild(childs[0]);
|
||||
}
|
||||
this.settingsDialogSearch.classList.add('hidden');
|
||||
this.settingsDialog.classList.add('hidden');
|
||||
Browser.switchCursorMode(true);
|
||||
}
|
||||
},
|
||||
|
||||
isDialogSearchDisplayed: function settings_isDialogSearchDisplayed() {
|
||||
return !this.settingsDialogSearch.classList.contains('hidden');
|
||||
},
|
||||
|
||||
handleDialogSearchSelected:
|
||||
function settings_handleDialogSearchSelected(ev) {
|
||||
if( ev ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
this.selectSearchEngine = ev.currentTarget.textContent;
|
||||
var childs = this.settingsDialogSearchSelectList.childNodes;
|
||||
var len = childs.length;
|
||||
for(var i = 0 ; i < len ; i ++) {
|
||||
if(childs[i].textContent == this.selectSearchEngine) {
|
||||
childs[i].firstChild.firstChild.dataset.display = 'selected';
|
||||
childs[i].lastChild.lastChild.classList.remove('off');
|
||||
childs[i].lastChild.lastChild.classList.add('on');
|
||||
} else {
|
||||
childs[i].firstChild.firstChild.dataset.display = '';
|
||||
childs[i].lastChild.lastChild.classList.remove('on');
|
||||
childs[i].lastChild.lastChild.classList.add('off');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleDialogSearchCancel: function settings_handleDialogSearchCancel(ev) {
|
||||
if( ev ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
// animation end event handler
|
||||
var end_event = (function() {
|
||||
Settings.settingsDialogSearchCancel.removeEventListener('transitionend',
|
||||
end_event, false);
|
||||
Settings.hideDialogSearch();
|
||||
});
|
||||
// end animantion event
|
||||
Settings.settingsDialogSearchCancel.addEventListener('transitionend',
|
||||
end_event, false);
|
||||
document.activeElement.classList.remove('active');
|
||||
},
|
||||
|
||||
handleDialogSearchOk: function settings_handleDialogSearchOk(ev) {
|
||||
if( ev ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
|
||||
return;
|
||||
}
|
||||
// animation end event handler
|
||||
var end_event = (function() {
|
||||
Settings.settingsDialogSearchOk.removeEventListener('transitionend',
|
||||
end_event, false);
|
||||
Settings.hideDialogSearch();
|
||||
});
|
||||
|
||||
var setSearchEngine = function () {
|
||||
this.currentSearchEngine = this.selectSearchEngine;
|
||||
this.settingsSearchEngineName.textContent =
|
||||
this.currentSearchEngine;
|
||||
Toolbar.setSearchEngine();
|
||||
};
|
||||
|
||||
// end animantion event
|
||||
Settings.settingsDialogSearchOk.addEventListener('transitionend',
|
||||
end_event, false);
|
||||
if( this.currentSearchEngine != this.selectSearchEngine ) {
|
||||
for(var i in this.searchEngineList) {
|
||||
if( this.selectSearchEngine == this.searchEngineList[i].name ) {
|
||||
SearchUtil.setSearchEngineId(this.searchEngineList[i].id,
|
||||
setSearchEngine.bind(this));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
document.activeElement.classList.remove('active');
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle Key Event.
|
||||
*/
|
||||
handleKeyEvent: function settings_handleKeyEvent(ev) {
|
||||
if( !Settings.isDisplayed() ) {
|
||||
return false;
|
||||
}
|
||||
// in the input area focus (= display keyboard)
|
||||
if(document.activeElement.nodeName == 'INPUT') {
|
||||
return true;
|
||||
}
|
||||
switch( ev.keyCode ) {
|
||||
case KeyEvent.DOM_VK_LEFT :
|
||||
ev.preventDefault();
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
if(( Settings.focusPos == 1 ) || ( Settings.focusPos == 4 )) {
|
||||
Settings.focusPos --;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( Settings.isDialogSearchDisplayed() ) {
|
||||
if( Settings.focusPos == Settings.focusList.length - 1 ) {
|
||||
Settings.focusPos --;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.DOM_VK_RIGHT :
|
||||
ev.preventDefault();
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
if(( Settings.focusPos === 0 ) || ( Settings.focusPos === 3 )) {
|
||||
Settings.focusPos ++;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( Settings.isDialogSearchDisplayed() ) {
|
||||
if( Settings.focusPos == Settings.focusList.length - 2 ) {
|
||||
Settings.focusPos ++;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.DOM_VK_UP :
|
||||
ev.preventDefault();
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
if(( Settings.focusPos === 2 ) || ( Settings.focusPos === 4 )) {
|
||||
Settings.focusPos -= 2;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
} else if( Settings.focusPos === 3 ) {
|
||||
Settings.focusPos --;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( Settings.isDialogSearchDisplayed() ) {
|
||||
if( Settings.focusPos > 0 ) {
|
||||
if( Settings.focusPos >= Settings.focusList.length - 1 ) {
|
||||
Settings.focusPos = Settings.focusList.length - 3;
|
||||
} else {
|
||||
Settings.focusPos --;
|
||||
}
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.DOM_VK_DOWN :
|
||||
ev.preventDefault();
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
if(( Settings.focusPos == 1 ) || ( Settings.focusPos === 2 )) {
|
||||
Settings.focusPos ++;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
} else if( Settings.focusPos === 0 ) {
|
||||
Settings.focusPos += 2;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( Settings.isDialogSearchDisplayed() ) {
|
||||
if( Settings.focusPos < Settings.focusList.length - 2 ) {
|
||||
Settings.focusPos ++;
|
||||
Settings.focusChange(Settings.focusPos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.DOM_VK_RETURN :
|
||||
ev.preventDefault();
|
||||
if( Settings.focusPos === 0 ) {
|
||||
Settings.focusList[Settings.focusPos].focus();
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
Awesomescreen.pointerImg.style.display = 'none';
|
||||
}
|
||||
}else{
|
||||
document.activeElement.classList.remove('active');
|
||||
document.activeElement.classList.add('active');
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.DOM_VK_BACK_SPACE :
|
||||
if( Settings.isDialogHomepageDisplayed() ) {
|
||||
Settings.hideDialogHomepage();
|
||||
return true;
|
||||
}
|
||||
if( Settings.isDialogSearchDisplayed() ) {
|
||||
Settings.hideDialogSearch();
|
||||
return true;
|
||||
}
|
||||
Settings.hide();
|
||||
return true;
|
||||
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
941
gaia/tv_apps/browser/js/smartList.js
Executable file
|
|
@ -0,0 +1,941 @@
|
|||
/* globals KeyEvent */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function(exports){
|
||||
|
||||
/**
|
||||
* Max number of list item visible when scroll.
|
||||
* @type {Number}
|
||||
*/
|
||||
const MAX_VISIBLE_ITEM = 7;
|
||||
|
||||
/**
|
||||
* Initial list element query number when first render list view
|
||||
* @type {Number}
|
||||
*/
|
||||
const INIT_LIST_ELEMENT_QUERY_NUMBER = 14;
|
||||
|
||||
/**
|
||||
* Max number of list html element can be render.
|
||||
* @type {Number}
|
||||
*/
|
||||
const MAX_LIST_ELEMENT = 21;
|
||||
|
||||
/**
|
||||
* Height of list item element, unit is rem.
|
||||
* @type {Number}
|
||||
*/
|
||||
const LIST_ITEM_HEIGHT = 12;
|
||||
|
||||
/**
|
||||
* 1rem equals to 10px
|
||||
* @type {Number}
|
||||
*/
|
||||
const PER_REM = 10;
|
||||
|
||||
/**
|
||||
* SmartList constructor
|
||||
* @param {Node} el - Target HTML element of SmartList.
|
||||
* @param {String} l10nTitle - Smart list title string.
|
||||
*/
|
||||
function SmartList(el, l10nTitle){
|
||||
this.el = el;
|
||||
|
||||
this.listEl = null;
|
||||
|
||||
this.listPointerEl = null;
|
||||
|
||||
this.l10nTitle = l10nTitle;
|
||||
|
||||
/**
|
||||
* Number of list item rendered.
|
||||
* @type {Number}
|
||||
*/
|
||||
this.listItemRenderNum = 0;
|
||||
|
||||
/**
|
||||
* Index of focus element in list.
|
||||
* @type {Number}
|
||||
*/
|
||||
this.focusIndex = 0;
|
||||
|
||||
/**
|
||||
* Index of list item start at.
|
||||
* @type {Number}
|
||||
*/
|
||||
this.listIndexStartAt = 0;
|
||||
|
||||
/**
|
||||
* Index of list item end at.
|
||||
* @type {Number}
|
||||
*/
|
||||
this.listIndexEndAt = 0;
|
||||
|
||||
/**
|
||||
* Index of visible list item start at.
|
||||
* @type {Number}
|
||||
*/
|
||||
this.listVisibleStartAt = 0;
|
||||
|
||||
/**
|
||||
* Current navigation folder.
|
||||
* @type {String}
|
||||
*/
|
||||
this.navState = null;
|
||||
|
||||
/**
|
||||
* Save user browser history.
|
||||
* @type {Array}
|
||||
*/
|
||||
this.navHistory = [];
|
||||
|
||||
/**
|
||||
* list items which in dom tree and been initialized but render with data
|
||||
* @type {Array}
|
||||
*/
|
||||
this.initializedItems = [];
|
||||
|
||||
/**
|
||||
* List item element map
|
||||
* @type {Object}
|
||||
*/
|
||||
this.listItemMap = {};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
SmartList.prototype = {
|
||||
constructor: SmartList,
|
||||
|
||||
/**
|
||||
* Create smart list dom tree architecture
|
||||
*/
|
||||
|
||||
/**
|
||||
* <div class="smart-list-container">
|
||||
* <h2 class="smart-list-title" data-l10n-id={this.l10nTitle}></h2>
|
||||
* <div class="smart-list-wrap">
|
||||
* <ul class="smart-list-view"></ul>
|
||||
* <div class="smart-list-mask"></div>
|
||||
* <div class="smart-list-pointer"></div>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
init: function() {
|
||||
var container = document.createElement('div'),
|
||||
title = document.createElement('h2'),
|
||||
listWrap = document.createElement('div'),
|
||||
listView = document.createElement('ul'),
|
||||
listMask = document.createElement('div'),
|
||||
listPointer = document.createElement('div');
|
||||
|
||||
container.classList.add('smart-list-container');
|
||||
title.classList.add('smart-list-title');
|
||||
title.setAttribute('data-l10n-id', this.l10nTitle);
|
||||
listWrap.classList.add('smart-list-wrap');
|
||||
listView.classList.add('smart-list-view');
|
||||
listMask.classList.add('smart-list-mask');
|
||||
listPointer.classList.add('smart-list-pointer');
|
||||
|
||||
container.appendChild(title);
|
||||
container.appendChild(listWrap);
|
||||
listWrap.appendChild(listView);
|
||||
listWrap.appendChild(listMask);
|
||||
listWrap.appendChild(listPointer);
|
||||
|
||||
this.el.classList.add('smart-list');
|
||||
this.el.addEventListener('keyup', this.handleListKeyUp.bind(this));
|
||||
this.el.appendChild(container);
|
||||
this.listEl = listView;
|
||||
this.listPointerEl = listPointer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open smart list.
|
||||
*/
|
||||
open: function() {
|
||||
this.reset();
|
||||
this.dispatchLoadDataByRange(0, INIT_LIST_ELEMENT_QUERY_NUMBER, null,
|
||||
(listData) => {
|
||||
this.el.classList.add('show');
|
||||
|
||||
if (listData.length > 0) {
|
||||
this.render(listData);
|
||||
var focusEl = this.listItemMap[0];
|
||||
this.pointerItem(focusEl);
|
||||
this.focusItem(focusEl);
|
||||
} else {
|
||||
this.focusItem(this.el);
|
||||
}
|
||||
|
||||
var event = new Event('open');
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Close smart list.
|
||||
*/
|
||||
close: function() {
|
||||
var event = new Event('close');
|
||||
this.navState = null;
|
||||
this.navHistory = [];
|
||||
this.el.classList.remove('show');
|
||||
this.el.dispatchEvent(event);
|
||||
if(this.isPointerDisplay()) {
|
||||
this.hideListPointer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset smart list
|
||||
*/
|
||||
reset: function(){
|
||||
var key = 0;
|
||||
|
||||
this.listEl.style.transform = 'translateY(0rem)';
|
||||
|
||||
this.focusIndex = 0;
|
||||
this.listVisibleStartAt = 0;
|
||||
this.listIndexStartAt = 0;
|
||||
this.listIndexEndAt = 0;
|
||||
|
||||
for (key in this.listItemMap) {
|
||||
if (this.listItemMap.hasOwnProperty(key)) {
|
||||
this.resetItem(this.listItemMap[key]);
|
||||
this.initializedItems.push(this.listItemMap[key]);
|
||||
delete this.listItemMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.hideListPointer();
|
||||
},
|
||||
|
||||
/**
|
||||
* Go back to previous folder. If there is no previous folder, close list.
|
||||
*/
|
||||
goBack: function() {
|
||||
if (!this.navState) {
|
||||
this.close();
|
||||
} else {
|
||||
this.navHistory.pop();
|
||||
this.navState = this.getCurNavHistory();
|
||||
var folderId = this.navState ? this.navState.folderId : null;
|
||||
this.reset();
|
||||
this.switchListView(folderId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render smart list
|
||||
* @param {Array} listData - Array of listData.
|
||||
*/
|
||||
render: function(listData){
|
||||
var itemEl = null,
|
||||
i = 0,
|
||||
length = listData.length;
|
||||
|
||||
for (; i < length; i++) {
|
||||
/*
|
||||
* if there are reseted list item, use these to render data,
|
||||
* else create new html element to render data
|
||||
*/
|
||||
if (this.initializedItems.length > 0) {
|
||||
itemEl = this.initializedItems.pop();
|
||||
this.updateItem(itemEl, listData[i]);
|
||||
} else {
|
||||
itemEl = this.createItem(listData[i]);
|
||||
this.listEl.appendChild(itemEl);
|
||||
this.listItemRenderNum++;
|
||||
}
|
||||
|
||||
itemEl.setAttribute('data-index', i);
|
||||
itemEl.style.transform =
|
||||
'translateY(' + (LIST_ITEM_HEIGHT * i) + 'rem)';
|
||||
this.listItemMap[i] = itemEl;
|
||||
}
|
||||
this.listIndexEndAt = length - 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is smart list opened.
|
||||
* @return {Boolean} - true when smart list opened.
|
||||
*/
|
||||
isDisplay: function() {
|
||||
return this.el.classList.contains('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Add new list item.
|
||||
* @param {Number} index - Index of new list item.
|
||||
* @param {Object} data - The list item data.
|
||||
*/
|
||||
addItem: function(index, data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
var itemEl = null,
|
||||
transform = 0;
|
||||
if (index < this.listIndexStartAt) {
|
||||
// add item to the beginning of the list
|
||||
if (this.initializedItems.length > 0) {
|
||||
itemEl = this.initializedItems.pop();
|
||||
this.updateItem(itemEl, data);
|
||||
} else if (this.listItemRenderNum === MAX_LIST_ELEMENT){
|
||||
itemEl = this.listItemMap[this.listIndexEndAt];
|
||||
this.updateItem(itemEl, data);
|
||||
delete this.listItemMap[this.listIndexEndAt];
|
||||
this.listIndexEndAt--;
|
||||
} else {
|
||||
itemEl = this.createItem(data);
|
||||
this.listEl.appendChild(itemEl);
|
||||
this.listItemRenderNum++;
|
||||
}
|
||||
this.listItemMap[index] = itemEl;
|
||||
itemEl.setAttribute('data-index', index);
|
||||
transform = (index) * LIST_ITEM_HEIGHT;
|
||||
itemEl.style.transform =
|
||||
'translateY(' + transform + 'rem)';
|
||||
this.listIndexStartAt = index;
|
||||
} else if (index > this.listIndexEndAt) {
|
||||
// add item to the end of the list
|
||||
if (this.initializedItems.length > 0) {
|
||||
itemEl = this.initializedItems.pop();
|
||||
this.updateItem(itemEl, data);
|
||||
} else if (this.listItemRenderNum === MAX_LIST_ELEMENT){
|
||||
itemEl = this.listItemMap[this.listIndexStartAt];
|
||||
this.updateItem(itemEl, data);
|
||||
delete this.listItemMap[this.listIndexStartAt];
|
||||
this.listIndexStartAt++;
|
||||
} else {
|
||||
itemEl = this.createItem(data);
|
||||
this.listEl.appendChild(itemEl);
|
||||
this.listItemRenderNum++;
|
||||
}
|
||||
this.listItemMap[index] = itemEl;
|
||||
itemEl.setAttribute('data-index', index);
|
||||
transform = (index) * LIST_ITEM_HEIGHT;
|
||||
itemEl.style.transform =
|
||||
'translateY(' + transform + 'rem)';
|
||||
this.listIndexEndAt = index;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create List Item.
|
||||
* @param {Object} data - list item data object
|
||||
*/
|
||||
|
||||
/**
|
||||
* <li class="list-item">
|
||||
* <div class="icon-box icon-box-left">
|
||||
* <div class="icon"></div>
|
||||
* </div>
|
||||
* <div class="text-box">
|
||||
* <span class="title"></span>
|
||||
* <span class="uri"></span>
|
||||
* </div>
|
||||
* <div class="icon-box icon-box-right">
|
||||
* <div class="icon" data-icon="arrow-right"></div>
|
||||
* </div>
|
||||
* </li>
|
||||
*/
|
||||
createItem: function(data) {
|
||||
var itemEl = document.createElement('li'),
|
||||
iconLeftEl = document.createElement('div'),
|
||||
iconImgLeftEl = document.createElement('div'),
|
||||
textEl = document.createElement('div'),
|
||||
titleEl = document.createElement('span'),
|
||||
uriEl = document.createElement('span'),
|
||||
iconRightEl = document.createElement('div'),
|
||||
iconImgRightEl = document.createElement('div');
|
||||
|
||||
itemEl.classList.add('list-item');
|
||||
itemEl.setAttribute('tabindex', '0');
|
||||
itemEl.setAttribute('data-type', data.type);
|
||||
iconLeftEl.classList.add('icon-box', 'icon-box-left');
|
||||
iconImgLeftEl.classList.add('icon');
|
||||
iconLeftEl.appendChild(iconImgLeftEl);
|
||||
|
||||
textEl.classList.add('text-box');
|
||||
titleEl.classList.add('title');
|
||||
uriEl.classList.add('uri');
|
||||
textEl.appendChild(titleEl);
|
||||
textEl.appendChild(uriEl);
|
||||
|
||||
iconRightEl.classList.add('icon-box', 'icon-box-right');
|
||||
iconImgRightEl.classList.add('icon');
|
||||
iconRightEl.appendChild(iconImgRightEl);
|
||||
|
||||
itemEl.appendChild(iconLeftEl);
|
||||
itemEl.appendChild(textEl);
|
||||
itemEl.appendChild(iconRightEl);
|
||||
|
||||
this.updateItemContent({
|
||||
item: itemEl,
|
||||
iconImgLeft: iconImgLeftEl,
|
||||
iconImgRight: iconImgRightEl,
|
||||
title: titleEl,
|
||||
uri: uriEl
|
||||
}, data);
|
||||
|
||||
// XXX: delegate these events later
|
||||
itemEl.
|
||||
addEventListener('keydown', this.handleItemKeyDown.bind(this));
|
||||
itemEl.
|
||||
addEventListener('keyup', this.handleItemKeyUp.bind(this));
|
||||
itemEl.
|
||||
addEventListener('mouseup', this.handleItemMouseUp.bind(this));
|
||||
itemEl.
|
||||
addEventListener('mouseover', this.handleItemMouseOver.bind(this));
|
||||
itemEl.
|
||||
addEventListener('mouseout', this.handleItemMouseOut.bind(this));
|
||||
|
||||
return itemEl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update list item element content.
|
||||
* @param {Node} el - HTML list item to update.
|
||||
* @param {Object} data - list item data object.
|
||||
*/
|
||||
updateItem: function(el, data) {
|
||||
var iconImgLeftEl = el.querySelector('.icon-box-left .icon'),
|
||||
titleEl = el.querySelector('.title'),
|
||||
uriEl = el.querySelector('.uri'),
|
||||
iconImgRightEl = el.querySelector('.icon-box-right .icon');
|
||||
|
||||
this.updateItemContent({
|
||||
item: el,
|
||||
iconImgLeft: iconImgLeftEl,
|
||||
iconImgRight: iconImgRightEl,
|
||||
title: titleEl,
|
||||
uri: uriEl
|
||||
}, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the content of the elements in an given item.
|
||||
* @param {Object} elems - all elements have to be updated.
|
||||
* @param {Object} data - list item data object.
|
||||
*/
|
||||
updateItemContent: function(elems, data) {
|
||||
var itemEl = elems.item,
|
||||
iconImgLeftEl = elems.iconImgLeft,
|
||||
iconImgRightEl = elems.iconImgRight,
|
||||
titleEl = elems.title,
|
||||
uriEl = elems.uri;
|
||||
|
||||
if (data.readOnly) {
|
||||
itemEl.setAttribute('readOnly', data.readOnly);
|
||||
} else {
|
||||
itemEl.setAttribute('readOnly', false);
|
||||
}
|
||||
|
||||
if (data.type) {
|
||||
itemEl.setAttribute('data-type', data.type);
|
||||
switch (data.type) {
|
||||
case 'folder':
|
||||
itemEl.setAttribute('data-folder', data.id);
|
||||
iconImgLeftEl.style.backgroundImage = '';
|
||||
iconImgLeftEl.setAttribute('data-icon', 'folder');
|
||||
titleEl.textContent = data.title;
|
||||
uriEl.textContent = '';
|
||||
iconImgRightEl.setAttribute('data-icon', 'arrow-right');
|
||||
break;
|
||||
case 'bookmark':
|
||||
itemEl.setAttribute('data-folder', '');
|
||||
if (data.iconUri) {
|
||||
iconImgLeftEl.style.backgroundImage = 'url(' + data.iconUri +')';
|
||||
iconImgLeftEl.setAttribute('data-icon', '');
|
||||
} else {
|
||||
iconImgLeftEl.style.backgroundImage = '';
|
||||
iconImgLeftEl.setAttribute('data-icon', 'default-fav');
|
||||
}
|
||||
titleEl.textContent = data.title ? data.title : data.uri;
|
||||
uriEl.textContent = data.uri;
|
||||
iconImgRightEl.setAttribute('data-icon', '');
|
||||
break;
|
||||
case 'button':
|
||||
itemEl.setAttribute('data-folder', '');
|
||||
iconImgLeftEl.style.backgroundImage = '';
|
||||
iconImgLeftEl.setAttribute('data-icon', 'arrow-left');
|
||||
titleEl.textContent = data.title;
|
||||
uriEl.textContent = '';
|
||||
iconImgRightEl.setAttribute('data-icon', '');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// The records without "type" will be recognized as history records.
|
||||
itemEl.setAttribute('data-type', 'bookmark');
|
||||
itemEl.setAttribute('data-folder', '');
|
||||
if (data.iconUri) {
|
||||
iconImgLeftEl.setAttribute('data-icon', '');
|
||||
iconImgLeftEl.style.backgroundImage = 'url(' + data.iconUri +')';
|
||||
} else {
|
||||
iconImgLeftEl.setAttribute('data-icon', 'default-fav');
|
||||
iconImgLeftEl.style.backgroundImage = '';
|
||||
}
|
||||
titleEl.textContent = data.title ? data.title : data.uri;
|
||||
uriEl.textContent = data.uri;
|
||||
iconImgRightEl.setAttribute('data-icon', '');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the focus list item.
|
||||
*/
|
||||
removeFocusItem: function() {
|
||||
var targetEl = null,
|
||||
i = 0;
|
||||
|
||||
targetEl = this.listItemMap[this.focusIndex];
|
||||
if (targetEl) {
|
||||
this.resetItem(targetEl);
|
||||
this.initializedItems.push(targetEl);
|
||||
|
||||
//reindex and update transform of the element behind the focus index
|
||||
for (i = this.focusIndex; i < this.listIndexEndAt; i++) {
|
||||
this.listItemMap[i] = this.listItemMap[i+1];
|
||||
targetEl = this.listItemMap[i];
|
||||
targetEl.setAttribute('data-index', i);
|
||||
this.shiftElement(targetEl, (-1 * LIST_ITEM_HEIGHT));
|
||||
}
|
||||
|
||||
delete this.listItemMap[this.listIndexEndAt];
|
||||
this.listIndexEndAt--;
|
||||
|
||||
// if the removed item is the last item of index,
|
||||
// focus the new last element
|
||||
if (this.listItemMap[this.focusIndex]) {
|
||||
this.focusItem(this.listItemMap[this.focusIndex]);
|
||||
} else if (this.listIndexEndAt !== -1) {
|
||||
this.focusIndex = this.listIndexEndAt;
|
||||
this.focusItem(this.listItemMap[this.focusIndex]);
|
||||
}
|
||||
|
||||
var event = new Event('itemRemoved');
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus list item.
|
||||
* @param {Node} el - HTML list item to focus.
|
||||
*/
|
||||
focusItem: function(el) {
|
||||
el.focus();
|
||||
},
|
||||
|
||||
pointerItem: function(el) {
|
||||
var pos = el.getBoundingClientRect(),
|
||||
transposX = (Math.ceil(pos.right) - 120 ) / PER_REM,
|
||||
transposY = (Math.ceil(pos.bottom) - 40 ) / PER_REM;
|
||||
|
||||
this.listPointerEl.style.transform =
|
||||
'translate(' + transposX + 'rem,' + transposY + 'rem)';
|
||||
this.showListPointer();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the First visible list item.
|
||||
*/
|
||||
focusFirstVisibleItem: function() {
|
||||
var targetEl = null;
|
||||
|
||||
// if there is list item rendered and the current active element not in
|
||||
// smart list, focus the first visible list item.
|
||||
if (this.listIndexEndAt !== -1 &&
|
||||
Array.prototype.indexOf.call(
|
||||
this.listEl.children,
|
||||
document.activeElement) === -1) {
|
||||
this.focusIndex = this.listVisibleStartAt;
|
||||
targetEl = this.listItemMap[this.focusIndex];
|
||||
if (targetEl) {
|
||||
this.pointerItem(targetEl);
|
||||
this.focusItem(targetEl);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset list item.
|
||||
* @param {Node} el - HTML list item to focus.
|
||||
*/
|
||||
resetItem: function(el) {
|
||||
var iconImgEl = el.querySelector('.icon-box-left .icon'),
|
||||
titleEl = el.querySelector('.title'),
|
||||
uriEl = el.querySelector('.uri');
|
||||
|
||||
el.setAttribute('data-index', -1);
|
||||
el.setAttribute('data-type', '');
|
||||
el.setAttribute('data-folder', '');
|
||||
el.setAttribute('readOnly', false);
|
||||
el.style.transform = '';
|
||||
iconImgEl.style.backgroundImage = '';
|
||||
iconImgEl.setAttribute('data-icon', '');
|
||||
titleEl.textContent = '';
|
||||
uriEl.textContent = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Preload the item out of visible view.
|
||||
* @param {Number} listIndex - preload list index.
|
||||
*/
|
||||
preloadItem: function(listIndex) {
|
||||
var dataIndex = 0;
|
||||
|
||||
if (this.navState) {
|
||||
if (listIndex === 0) {
|
||||
var data = this.generateBackButtonData(this.navState.folderTitle);
|
||||
this.addItem(0, data);
|
||||
} else {
|
||||
dataIndex = listIndex - 1;
|
||||
this.dispatchLoadDataByIndex(
|
||||
listIndex,
|
||||
dataIndex,
|
||||
this.navState.folderId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dataIndex = listIndex;
|
||||
this.dispatchLoadDataByIndex(
|
||||
listIndex,
|
||||
dataIndex,
|
||||
null
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shift list item transform
|
||||
* @param {Node} el - list item element to transform
|
||||
* @param {Nunber} shift - shift transform, unit is rem
|
||||
*/
|
||||
shiftElement: function(el, shift) {
|
||||
var style = el.style.transform.split('('),
|
||||
transform = parseInt(style[1], 10);
|
||||
transform += shift;
|
||||
el.style.transform =
|
||||
'translateY(' + transform + 'rem) translateZ(0.01px)';
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Focus list element's index
|
||||
*/
|
||||
getFocusItemIndex: function() {
|
||||
return this.focusIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Focus list element's uri content
|
||||
*/
|
||||
getFocusItemUri: function() {
|
||||
var targetEl = this.listItemMap[this.focusIndex];
|
||||
return targetEl ? targetEl.querySelector('.uri').textContent : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Focus list element's title content
|
||||
*/
|
||||
getFocusItemTitle: function() {
|
||||
var targetEl = this.listItemMap[this.focusIndex];
|
||||
return targetEl ? targetEl.querySelector('.title').textContent : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update focus list element's title content
|
||||
* @param {String} title - title to update
|
||||
*/
|
||||
updateFocusItemTitle: function(title) {
|
||||
var targetEl = this.listItemMap[this.focusIndex];
|
||||
|
||||
if (targetEl) {
|
||||
targetEl.querySelector('.title').textContent = title;
|
||||
this.focusItem(targetEl);
|
||||
var event = new Event('itemUpdated');
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
isPointerDisplay: function() {
|
||||
return this.listPointerEl.classList.contains('show');
|
||||
},
|
||||
|
||||
showListPointer: function() {
|
||||
this.listPointerEl.classList.add('show');
|
||||
},
|
||||
|
||||
hideListPointer: function() {
|
||||
this.listPointerEl.classList.remove('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Add folder id and folder title
|
||||
* @param {[type]} folderId [description]
|
||||
* @param {[type]} folderTitle [description]
|
||||
*/
|
||||
addNavHistory: function(folderId, folderTitle) {
|
||||
this.navHistory.push({
|
||||
folderId: folderId,
|
||||
folderTitle: folderTitle
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current user browse state.
|
||||
* @return {String} current user browse folder.
|
||||
*/
|
||||
getCurNavHistory: function() {
|
||||
var len = this.navHistory.length;
|
||||
if (len > 0) {
|
||||
return this.navHistory[len - 1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate back button data.
|
||||
* @return {Object} - back button data
|
||||
*/
|
||||
generateBackButtonData: function(title) {
|
||||
return {
|
||||
type: 'button',
|
||||
title: 'Back to ' + title
|
||||
};
|
||||
},
|
||||
|
||||
switchListView: function(folderId) {
|
||||
this.dispatchLoadDataByRange(0, INIT_LIST_ELEMENT_QUERY_NUMBER, folderId,
|
||||
(listData) => {
|
||||
// if user is browser list data in a folder,
|
||||
// add back button data at the first place of listData
|
||||
if (this.navState) {
|
||||
listData.unshift(
|
||||
this.generateBackButtonData(this.navState.folderTitle)
|
||||
);
|
||||
}
|
||||
|
||||
if (listData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.render(listData);
|
||||
|
||||
// focus the first item in list. if the first item is button and list
|
||||
// item length is bigger than 1, focus the second item
|
||||
this.focusIndex = 0;
|
||||
if (this.navState && this.listIndexEndAt > 0) {
|
||||
this.focusIndex = 1;
|
||||
}
|
||||
var focusEl = this.listItemMap[this.focusIndex];
|
||||
this.pointerItem(focusEl);
|
||||
this.focusItem(focusEl);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move focus to the previous element
|
||||
*/
|
||||
moveFocusIndexUp: function() {
|
||||
this.focusIndex -= 1;
|
||||
var preloadItemIndex = this.focusIndex - MAX_VISIBLE_ITEM;
|
||||
if (preloadItemIndex >= 0 && preloadItemIndex < this.listIndexStartAt) {
|
||||
this.preloadItem(preloadItemIndex);
|
||||
}
|
||||
|
||||
// if the new focus element is before this.listVisibleStartAt,
|
||||
// update list item's transform to shift down
|
||||
var targetEl = this.listItemMap[this.focusIndex];
|
||||
if (this.focusIndex < this.listVisibleStartAt) {
|
||||
this.listVisibleStartAt--;
|
||||
this.shiftElement(this.listEl, LIST_ITEM_HEIGHT);
|
||||
this.focusItem(targetEl);
|
||||
} else {
|
||||
this.pointerItem(targetEl);
|
||||
this.focusItem(targetEl);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move focus to the next element
|
||||
*/
|
||||
moveFocusIndexDown: function() {
|
||||
this.focusIndex += 1;
|
||||
|
||||
// pre-load list item later then listIndexEndAt
|
||||
var preloadItemIndex = this.focusIndex + MAX_VISIBLE_ITEM;
|
||||
if (preloadItemIndex > this.listIndexEndAt) {
|
||||
this.preloadItem(preloadItemIndex);
|
||||
}
|
||||
|
||||
// if the new focus element is after this.listIndexEndAt,
|
||||
// update list item's transform to shift up
|
||||
var targetEl = this.listItemMap[this.focusIndex];
|
||||
if ((this.focusIndex - this.listVisibleStartAt) ===
|
||||
MAX_VISIBLE_ITEM ) {
|
||||
this.listVisibleStartAt++;
|
||||
this.shiftElement(this.listEl, (-1 * LIST_ITEM_HEIGHT));
|
||||
this.focusItem(targetEl);
|
||||
} else {
|
||||
this.pointerItem(targetEl);
|
||||
this.focusItem(targetEl);
|
||||
}
|
||||
},
|
||||
|
||||
handleListKeyUp: function(e) {
|
||||
switch(e.keyCode){
|
||||
case KeyEvent.DOM_VK_ESCAPE:
|
||||
case KeyEvent.DOM_VK_BACK_SPACE:
|
||||
this.goBack();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
handleItemKeyDown: function(e) {
|
||||
switch(e.keyCode){
|
||||
case KeyEvent.DOM_VK_UP:
|
||||
if(this.focusIndex > this.listIndexStartAt) {
|
||||
this.moveFocusIndexUp();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.DOM_VK_DOWN:
|
||||
if(this.focusIndex < this.listIndexEndAt) {
|
||||
this.moveFocusIndexDown();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
handleItemKeyUp: function(e) {
|
||||
switch(e.keyCode){
|
||||
case KeyEvent.DOM_VK_SUBMENU:
|
||||
this.handleKeySubmenu(e);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RETURN:
|
||||
this.handleKeyReturn(e);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
handleItemMouseUp: function(e) {
|
||||
switch(e.button){
|
||||
case 0 :
|
||||
// left click
|
||||
this.handleKeyReturn(e);
|
||||
break;
|
||||
case 2:
|
||||
//right click
|
||||
this.handleKeySubmenu(e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleItemMouseOver: function(e) {
|
||||
var targetEl = e.currentTarget;
|
||||
if (targetEl) {
|
||||
var dataIndex = targetEl.getAttribute('data-index');
|
||||
var event = new CustomEvent('mouseOverItem', {detail: targetEl});
|
||||
targetEl.focus();
|
||||
this.focusIndex = parseInt(dataIndex, 10);
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleItemMouseOut: function(e) {
|
||||
var targetEl = e.currentTarget;
|
||||
if (targetEl) {
|
||||
this.hideListPointer();
|
||||
targetEl.blur();
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyReturn: function(e) {
|
||||
var targetEl = e.currentTarget,
|
||||
type = targetEl.getAttribute('data-type'),
|
||||
folderId = null;
|
||||
|
||||
switch(type) {
|
||||
case 'folder':
|
||||
var folderTitle = targetEl.querySelector('.title').textContent;
|
||||
|
||||
folderId = targetEl.getAttribute('data-folder');
|
||||
this.addNavHistory(folderId, folderTitle);
|
||||
this.navState = this.getCurNavHistory();
|
||||
this.reset();
|
||||
this.switchListView(folderId);
|
||||
break;
|
||||
case 'bookmark':
|
||||
var uriEL = targetEl.querySelector('.uri');
|
||||
if (uriEL) {
|
||||
this.dispatchDisplayWebsite(uriEL.textContent);
|
||||
this.close();
|
||||
}
|
||||
break;
|
||||
case 'button':
|
||||
this.navHistory.pop();
|
||||
this.navState = this.getCurNavHistory();
|
||||
folderId = this.navState ? this.navState.folderId : null;
|
||||
this.reset();
|
||||
this.switchListView(folderId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleKeySubmenu: function(e) {
|
||||
var targetEl = e.currentTarget;
|
||||
if (targetEl.getAttribute('data-type') === 'bookmark') {
|
||||
var event = new CustomEvent('showSubMenu', {
|
||||
detail: {
|
||||
readOnly: targetEl.getAttribute('readOnly')
|
||||
}
|
||||
});
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
|
||||
dispatchDisplayWebsite: function(uri) {
|
||||
var event = new CustomEvent('displayWebsite', {detail: uri});
|
||||
this.el.dispatchEvent(event);
|
||||
},
|
||||
|
||||
dispatchLoadDataByRange: function(start, number, folderId, cb) {
|
||||
var eventDetail = {
|
||||
startAt: start,
|
||||
number: number,
|
||||
folderId: folderId,
|
||||
callback: cb
|
||||
};
|
||||
var event = new CustomEvent('loadDataByRange', {detail: eventDetail});
|
||||
this.el.dispatchEvent(event);
|
||||
},
|
||||
|
||||
dispatchLoadDataByIndex: function(listIndex, dataIndex, navState) {
|
||||
var event = new CustomEvent('loadDataByIndex', {
|
||||
detail: {
|
||||
listIndex: listIndex,
|
||||
dataIndex: dataIndex,
|
||||
folderId: navState
|
||||
}});
|
||||
this.el.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
exports.SmartList = SmartList;
|
||||
})(window);
|
||||
107
gaia/tv_apps/browser/js/sync/bookmarks.js
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
/* exported SyncBookmark */
|
||||
/* global SyncBrowserDB */
|
||||
/* global SyncDsHelper */
|
||||
|
||||
var SyncBookmark = (function () {
|
||||
var syncDataStore;
|
||||
|
||||
function updateWrapper(data) {
|
||||
return new Promise(resolve => {
|
||||
if (data.deleted) {
|
||||
SyncBrowserDB.removeBookmark(data.id, () => {
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
SyncBrowserDB.updateRawBookmark(data, () => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleTask(task) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var data = [];
|
||||
if (task.data && task.data.fxsyncRecords) {
|
||||
for (var key in task.data.fxsyncRecords) {
|
||||
data.push(task.data.fxsyncRecords[key]);
|
||||
}
|
||||
}
|
||||
switch(task.operation) {
|
||||
case 'update':
|
||||
case 'add':
|
||||
Promise.all(data.map(updateWrapper)).then(resolve);
|
||||
break;
|
||||
case 'clear':
|
||||
// Ignore clear operation.
|
||||
resolve();
|
||||
break;
|
||||
case 'remove':
|
||||
getBookmarkByDsId(task.id, bookmark => {
|
||||
if (bookmark) {
|
||||
SyncBrowserDB.removeBookmark(bookmark.id, () => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'done':
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getBookmarkByDsId(dsid, callback) {
|
||||
SyncBrowserDB.getBookmark({bmkUri: dsid}, bookmark => {
|
||||
if (bookmark) {
|
||||
callback(bookmark);
|
||||
} else {
|
||||
var specialType =
|
||||
['query', 'microsummary', 'folder', 'livemark', 'separator'];
|
||||
var isSpecialType = specialType.some(value => {
|
||||
return dsid.indexOf(value + '|') === 0;
|
||||
});
|
||||
var tmp = dsid.split('|');
|
||||
if (isSpecialType && tmp.length === 2) {
|
||||
var fxsyncId = tmp[1];
|
||||
SyncBrowserDB.getBookmark(fxsyncId, callback);
|
||||
} else {
|
||||
console.warn('trying to delete a unknown type?', dsid);
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (syncDataStore) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
syncDataStore = new SyncDsHelper('bookmarks_store');
|
||||
return syncDataStore.init().then(() => {
|
||||
return syncDataStore.start(handleTask);
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!syncDataStore) {
|
||||
return Promise.reject('Uninitialized DataStore');
|
||||
}
|
||||
return syncDataStore.stop().then(() => {
|
||||
syncDataStore = null;
|
||||
return SyncBrowserDB.clearBookmarks();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
stop: stop
|
||||
};
|
||||
})();
|
||||
1958
gaia/tv_apps/browser/js/sync/db.js
Executable file
113
gaia/tv_apps/browser/js/sync/ds_helper.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
/* exported SyncDsHelper */
|
||||
/* global localStorage */
|
||||
|
||||
var SyncDsHelper = function (dataStoreName) {
|
||||
if (typeof dataStoreName !== 'string') {
|
||||
throw new Event('Incorrect DataStore name.');
|
||||
}
|
||||
this.dataStoreName = dataStoreName;
|
||||
this.dataStoreRevId = dataStoreName + '::revId';
|
||||
};
|
||||
|
||||
SyncDsHelper.prototype = {
|
||||
store: null,
|
||||
dataChangeHandler: null,
|
||||
|
||||
init() {
|
||||
return this._ensureStore();
|
||||
},
|
||||
|
||||
_ensureStore() {
|
||||
if (this.store) {
|
||||
return Promise.resolve(this.store);
|
||||
}
|
||||
return navigator.getDataStores(this.dataStoreName).then(stores => {
|
||||
this.store = stores[0];
|
||||
return this.store;
|
||||
});
|
||||
},
|
||||
|
||||
setSyncedRevId(revId) {
|
||||
localStorage.setItem(this.dataStoreRevId, revId);
|
||||
},
|
||||
|
||||
getSyncedRevId() {
|
||||
return localStorage.getItem(this.dataStoreRevId);
|
||||
},
|
||||
|
||||
registerStoreChangeEvent(handler) {
|
||||
this._ensureStore().then(() => {
|
||||
if (!this.dataChangeHandler) {
|
||||
this.store.removeEventListener('change', this.dataChangeHandler);
|
||||
}
|
||||
this.dataChangeHandler = handler;
|
||||
this.store.addEventListener('change', this.dataChangeHandler);
|
||||
});
|
||||
},
|
||||
|
||||
unregisterStoreChangeEvent() {
|
||||
return this._ensureStore().then(() => {
|
||||
this.store.removeEventListener('change', this.dataChangeHandler);
|
||||
this.dataChangeHandler = null;
|
||||
});
|
||||
},
|
||||
|
||||
clear() {
|
||||
localStorage.removeItem(this.dataStoreRevId);
|
||||
return this._ensureStore().then(() => {
|
||||
return this.store.clear();
|
||||
});
|
||||
},
|
||||
|
||||
start(handleTask) {
|
||||
return this.init().then(() => {
|
||||
this.registerStoreChangeEvent(() => {
|
||||
this.dataStoreSync(handleTask);
|
||||
});
|
||||
return this.dataStoreSync(handleTask);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
return this.unregisterStoreChangeEvent().then(this.clear.bind(this));
|
||||
},
|
||||
|
||||
dataStoreSync(handle) {
|
||||
return this._ensureStore().then(() => {
|
||||
var _revId = this.getSyncedRevId();
|
||||
return new Promise((resolve, reject) => {
|
||||
var runNextTask = (cursor) => {
|
||||
cursor.next().then(task => {
|
||||
manageTask(cursor, task);
|
||||
});
|
||||
};
|
||||
|
||||
var post = (cursor, task) => {
|
||||
if (task.operation === 'done') {
|
||||
this.setSyncedRevId(this.store.revisionId);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
runNextTask(cursor);
|
||||
};
|
||||
|
||||
var manageTask = (cursor, task) => {
|
||||
if (handle) {
|
||||
handle(task).then(() => post(cursor, task));
|
||||
} else {
|
||||
post(cursor, task);
|
||||
}
|
||||
};
|
||||
|
||||
runNextTask(this.store.sync(_revId));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
86
gaia/tv_apps/browser/js/sync/history.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
/* exported SyncHistory */
|
||||
/* global SyncBrowserDB */
|
||||
/* global SyncDsHelper */
|
||||
|
||||
/*
|
||||
exampleHistroyInIndexedDB = {
|
||||
"uri": "http://example.com/",
|
||||
"title": "Hello World",
|
||||
"frecency": 1,
|
||||
"iconUri": "http://example.com/favicon.ico",
|
||||
"screenshot": {}
|
||||
}
|
||||
*/
|
||||
|
||||
var SyncHistory = (function () {
|
||||
var syncDataStore;
|
||||
|
||||
function handleTask(task) {
|
||||
return new Promise(resolve => {
|
||||
switch(task.operation) {
|
||||
case 'update':
|
||||
case 'add':
|
||||
var data = {};
|
||||
var syncedData = task.data;
|
||||
if (syncedData && syncedData.url &&
|
||||
Array.isArray(syncedData.visits) && syncedData.visits.length > 0) {
|
||||
data.uri = syncedData.url;
|
||||
data.title = syncedData.title;
|
||||
data.fxsyncId = syncedData.fxsyncId;
|
||||
data.timestamp = syncedData.visits[0];
|
||||
} else {
|
||||
console.warn('Invalid places record:', task);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
SyncBrowserDB.updateRawHistory(data, () => {
|
||||
resolve();
|
||||
});
|
||||
break;
|
||||
case 'clear':
|
||||
// Ignore clear operation.
|
||||
resolve();
|
||||
break;
|
||||
case 'remove':
|
||||
SyncBrowserDB.removeHistory(task.id, () => {
|
||||
resolve();
|
||||
});
|
||||
break;
|
||||
case 'done':
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (syncDataStore) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
syncDataStore = new SyncDsHelper('places');
|
||||
return syncDataStore.init().then(() => {
|
||||
return syncDataStore.start(handleTask);
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!syncDataStore) {
|
||||
return Promise.reject('Uninitialized DataStore');
|
||||
}
|
||||
return syncDataStore.stop().then(() => {
|
||||
syncDataStore = null;
|
||||
return SyncBrowserDB.clearHistoryDeep();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
stop: stop
|
||||
};
|
||||
})();
|
||||
163
gaia/tv_apps/browser/js/sync/manager_bridge.js
Executable file
|
|
@ -0,0 +1,163 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* This module acts as a bridge between the browser UI and the Sync manager
|
||||
* that lives in the System application.
|
||||
*
|
||||
* It allows consumers to subscribe to updates done in the Sync state machine
|
||||
* so they can update the UI accordingly.
|
||||
*
|
||||
* The simple high level picture of Sync in the browser is:
|
||||
*
|
||||
* settings.js toolbar.js
|
||||
* | |
|
||||
* | |
|
||||
* \______ manager_bridge.js _______/
|
||||
* |
|
||||
* | IAC
|
||||
* |
|
||||
* smart_system/sync_manager.js
|
||||
*
|
||||
*/
|
||||
|
||||
/* global LazyLoader */
|
||||
/* global SyncBookmark */
|
||||
/* global SyncHistory */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const DEBUG = false;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
console.log('[SyncManagerBridge] ' + Array.slice(arguments).concat());
|
||||
}
|
||||
}
|
||||
|
||||
var SyncManagerBridge = {
|
||||
_port: null,
|
||||
_requests: new Map(),
|
||||
_listeners: new Map(),
|
||||
|
||||
addListener(listener) {
|
||||
debug('addListener', listener);
|
||||
if (!this._listeners.has(listener)) {
|
||||
this._listeners.set(listener);
|
||||
}
|
||||
},
|
||||
|
||||
removeListener(listener) {
|
||||
debug('removeListener', listener);
|
||||
if (!this._listeners.has(listener)) {
|
||||
return;
|
||||
}
|
||||
this._listeners.delete(listener);
|
||||
},
|
||||
|
||||
connect() {
|
||||
if (this._port) {
|
||||
return Promise.resolve(this._port);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.mozApps.getSelf().onsuccess = event => {
|
||||
var app = event.target.result;
|
||||
app.connect('gaia-sync-management').then(ports => {
|
||||
if (!ports || !ports.length) {
|
||||
return reject();
|
||||
}
|
||||
this._port = ports[0];
|
||||
this._port.onmessage = this.onmessage.bind(this);
|
||||
resolve(this._port);
|
||||
}).catch(reject);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
iacRequest(request) {
|
||||
debug('iacRequest', JSON.stringify(request));
|
||||
return new Promise(resolve => {
|
||||
this.connect().then(port => {
|
||||
if (request.id) {
|
||||
this._requests.set(request.id, resolve);
|
||||
}
|
||||
port.postMessage(request);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onmessage(event) {
|
||||
var self = this;
|
||||
function notify() {
|
||||
if (self._listeners.size) {
|
||||
Array.from(self._listeners.keys()).forEach(listener => {
|
||||
listener(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var message = event.data;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('message', JSON.stringify(message));
|
||||
|
||||
if (message.id && this._requests.has(message.id)) {
|
||||
this._requests.get(message.id)(message);
|
||||
this._requests.delete(message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.name !== 'onsyncchange') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.state == 'disabled' ||
|
||||
message.state == 'enabled') {
|
||||
LazyLoader.load([
|
||||
'js/sync/ds_helper.js',
|
||||
'js/sync/bookmarks.js',
|
||||
'js/sync/history.js'
|
||||
]).then(() => {
|
||||
var method = message.state == 'enabled' ? 'start'
|
||||
: 'stop';
|
||||
Promise.all([
|
||||
SyncBookmark[method](),
|
||||
SyncHistory[method]()
|
||||
]).catch(error => {
|
||||
console.warn(error);
|
||||
});
|
||||
|
||||
notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
notify();
|
||||
},
|
||||
|
||||
getInfo() {
|
||||
return this.iacRequest({
|
||||
name: 'getInfo',
|
||||
id: Date.now()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
['enable',
|
||||
'disable',
|
||||
'sync'].forEach(requestName => {
|
||||
SyncManagerBridge[requestName] = () => {
|
||||
return SyncManagerBridge.iacRequest({
|
||||
name: requestName
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
exports.SyncManagerBridge = SyncManagerBridge;
|
||||
|
||||
}(window));
|
||||
507
gaia/tv_apps/browser/js/sync/settings.js
Executable file
|
|
@ -0,0 +1,507 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global Awesomescreen */
|
||||
/* global Browser */
|
||||
/* global BrowserDialog */
|
||||
/* global ERROR_DIALOG_CLOSED_BY_USER */
|
||||
/* global ERROR_INVALID_SYNC_ACCOUNT */
|
||||
/* global ERROR_OFFLINE */
|
||||
/* global ERROR_SYNC_APP_KILLED */
|
||||
/* global ERROR_UNKNOWN */
|
||||
/* global LazyLoader */
|
||||
/* global Settings */
|
||||
/* global SettingsListener */
|
||||
/* global SyncManagerBridge */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
const DEBUG = false;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
console.log('[FirefoxSyncSettings] ' + Array.slice(arguments).concat());
|
||||
}
|
||||
}
|
||||
|
||||
// Screens
|
||||
const ENABLED = 'enabled';
|
||||
const DISABLED = 'disabled';
|
||||
|
||||
const BOOKMARKS_SETTING = 'sync.collections.bookmarks.enabled';
|
||||
const HISTORY_SETTING = 'sync.collections.history.enabled';
|
||||
|
||||
const ELEMENTS = [{
|
||||
screen: DISABLED,
|
||||
selector: '#fxsync-sign-in-button',
|
||||
event: 'mouseup',
|
||||
listener: 'enable'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-signed-in-as'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-sync-now-button',
|
||||
event: 'mouseup',
|
||||
listener: 'sync'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-sign-out-button',
|
||||
event: 'mouseup',
|
||||
listener: 'disable'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-collection-bookmarks',
|
||||
event: 'click',
|
||||
listener: 'onbookmarkschecked',
|
||||
init: 'onbookmarkschange'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-collection-history',
|
||||
event: 'click',
|
||||
listener: 'onhistorychecked',
|
||||
init: 'onhistorychange'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-tos',
|
||||
event: 'click',
|
||||
listener: 'openTos'
|
||||
}, {
|
||||
screen: ENABLED,
|
||||
selector: '#fxsync-privacy',
|
||||
event: 'click',
|
||||
listener: 'openPrivacy'
|
||||
}];
|
||||
|
||||
function getElementName(str) {
|
||||
str = str.toLowerCase().replace('#fxsync-', '');
|
||||
return str.replace(/-([a-z])/g, g => {
|
||||
return g[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
var FirefoxSyncSettings = {
|
||||
init() {
|
||||
this.area = document.querySelector('#fxsync-area');
|
||||
this.elements = {
|
||||
enabled: this.area.querySelector('#fxsync-enabled'),
|
||||
disabled: this.area.querySelector('#fxsync-disabled')
|
||||
};
|
||||
this.listeners = new Map();
|
||||
this.collections = new Map();
|
||||
this.screen = null;
|
||||
this.state = 'disabled';
|
||||
|
||||
this.showScreen(DISABLED);
|
||||
|
||||
LazyLoader.load('shared/js/settings_listener.js').then(() => {
|
||||
[{
|
||||
name: BOOKMARKS_SETTING,
|
||||
onchange: 'onbookmarkschange'
|
||||
}, {
|
||||
name: HISTORY_SETTING,
|
||||
onchange: 'onhistorychange'
|
||||
}].forEach(setting => {
|
||||
SettingsListener.observe(setting.name, true, enabled => {
|
||||
debug(setting.name + ' changed to ' + enabled);
|
||||
enabled ? this.collections.set(setting.name, enabled)
|
||||
: this.collections.delete(setting.name);
|
||||
this[setting.onchange]();
|
||||
this.maybeEnableSyncNow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
SyncManagerBridge.addListener(this.onsyncchange.bind(this));
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
debug('Visibility change', document.hidden);
|
||||
if (!document.hidden) {
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
refresh() {
|
||||
debug('Refreshing');
|
||||
SyncManagerBridge.getInfo().then(this.onsyncchange.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* This is where most part of the Sync settings logic lives.
|
||||
*
|
||||
* Here we handle Sync lifecycle events coming from the SyncManager
|
||||
* through the SyncMangerBridge. We react to these events by
|
||||
* changing the screen depending on the state.
|
||||
*
|
||||
* We show two possible different state:
|
||||
* 1. Logged out screen. We simply show the user what Sync is and
|
||||
* allow her to start using it or to keep walking.
|
||||
* 2. Logged in screen. We show to the user several different options:
|
||||
* + The user owning the Sync account. If the account belongs to a
|
||||
* verified user that previosly used Sync, we are fine, if not, we
|
||||
* show a warning asking the user to go to other Firefox client to
|
||||
* create a FxA and login into Sync.
|
||||
* + A button to disable sync. Clicking it will log the user out from
|
||||
* FxA and will clear the synchronized data.
|
||||
* + A button to start a sync on demand. Clicking it will disable the
|
||||
* button and the collections switches while the synchronization is
|
||||
* being performed. Once the sync is done successfully, we enable
|
||||
* all the options back and refresh the last sync time.
|
||||
* We disable this button if no collection is selected.
|
||||
* + Collections switches. Allow the user to select which collections
|
||||
* she wants to keep synchronized. If no collection is selected, the
|
||||
* synchronization will be disabled.
|
||||
* + ToS and Privacy links.
|
||||
*/
|
||||
|
||||
onsyncchange(message) {
|
||||
debug(JSON.stringify(message));
|
||||
if (!message || !message.state) {
|
||||
throw new Error('Missing sync state');
|
||||
}
|
||||
switch (message.state) {
|
||||
case 'disabled':
|
||||
// We want to show a dialog once Sync is disabled
|
||||
// but we only want to do that if it's disabled via user action
|
||||
// and the user has already logged in.
|
||||
// (and not because it is already disabled from a previous run).
|
||||
if (this.state === 'disabling' &&
|
||||
(this.previousState === 'enabled' ||
|
||||
this.previousState === 'syncing')) {
|
||||
navigator.mozL10n.formatValue('fxsync-disabled').then(result => {
|
||||
window.alert(result);
|
||||
});
|
||||
}
|
||||
this.showScreen(DISABLED);
|
||||
this.hideEnabling();
|
||||
break;
|
||||
case 'enabling':
|
||||
this.showEnabling();
|
||||
break;
|
||||
case 'enabled':
|
||||
// We want to show a dialog once Sync is enabled
|
||||
// but we only want to do that if its enabled via user action
|
||||
// (and not because it is already enabled from a previous run).
|
||||
if (this.state === 'enabling') {
|
||||
navigator.mozL10n.formatValue('fxsync-enabled').then(result => {
|
||||
window.alert(result);
|
||||
});
|
||||
}
|
||||
this.hideEnabling();
|
||||
this.showScreen(ENABLED);
|
||||
this.showUser(message.user);
|
||||
this.showSyncNow();
|
||||
break;
|
||||
case 'syncing':
|
||||
this.showScreen(ENABLED);
|
||||
// In case that the user opens the browser app while we are on a
|
||||
// syncing state the previous 'enabled' state is missed and so
|
||||
// we need to make sure that we show the user here as well.
|
||||
this.showUser(message.user);
|
||||
this.showSyncing();
|
||||
break;
|
||||
case 'errored':
|
||||
LazyLoader.load('shared/js/sync/errors.js', () => {
|
||||
const IGNORED_ERRORS = [
|
||||
ERROR_DIALOG_CLOSED_BY_USER,
|
||||
ERROR_SYNC_APP_KILLED
|
||||
];
|
||||
|
||||
if (IGNORED_ERRORS.indexOf(message.error) > -1) {
|
||||
Settings.hide.bind(Settings)();
|
||||
return;
|
||||
}
|
||||
|
||||
var errorIds = [ERROR_UNKNOWN, ERROR_UNKNOWN + '-explanation'];
|
||||
|
||||
const KNOWN_ERRORS = [
|
||||
ERROR_INVALID_SYNC_ACCOUNT,
|
||||
ERROR_OFFLINE
|
||||
];
|
||||
|
||||
if (KNOWN_ERRORS.indexOf(message.error) > -1) {
|
||||
errorIds[0] = message.error;
|
||||
errorIds[1] = message.error + '-explanation';
|
||||
}
|
||||
|
||||
var l10n = navigator.mozL10n;
|
||||
Promise.all(
|
||||
errorIds.map(l10n.formatValue.bind(l10n))
|
||||
).then(result => {
|
||||
window.alert(result[0] + '\n' + result[1], () => {
|
||||
this.showScreen(DISABLED);
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
this.previousState = this.state;
|
||||
this.state = message.state;
|
||||
},
|
||||
|
||||
onbookmarkschange() {
|
||||
debug('onbookmarkschange');
|
||||
var needsCleanup;
|
||||
if (!this.elements.collectionBookmarks) {
|
||||
// We may observe a setting change once we already disabled Sync
|
||||
// because the Sync Manager is reverting user preference changes to
|
||||
// the default ones. In that case, we need to create the object
|
||||
// briefly, so we can update the DOM accordingly.
|
||||
this.elements.collectionBookmarks = this.area.querySelector(
|
||||
'#fxsync-collection-bookmarks'
|
||||
);
|
||||
needsCleanup = true;
|
||||
}
|
||||
|
||||
this.elements.collectionBookmarks.checked =
|
||||
this.collections.has(BOOKMARKS_SETTING);
|
||||
|
||||
if (needsCleanup) {
|
||||
this.elements.collectionBookmarks = null;
|
||||
}
|
||||
},
|
||||
|
||||
onbookmarkschecked() {
|
||||
debug('onbookmarkschecked');
|
||||
this.oncollectionchecked(this.elements.collectionBookmarks,
|
||||
BOOKMARKS_SETTING);
|
||||
},
|
||||
|
||||
onhistorychange() {
|
||||
debug('onhistorychange');
|
||||
var needsCleanup;
|
||||
if (!this.elements.collectionHistory) {
|
||||
// We may observe a setting change once we already disabled Sync
|
||||
// because the Sync Manager is reverting user preference changes to
|
||||
// the default ones. In that case, we need to create the object
|
||||
// briefly, so we can update the DOM accordingly.
|
||||
this.elements.collectionHistory = this.area.querySelector(
|
||||
'#fxsync-collection-history'
|
||||
);
|
||||
needsCleanup = true;
|
||||
}
|
||||
|
||||
this.elements.collectionHistory.checked =
|
||||
this.collections.has(HISTORY_SETTING);
|
||||
|
||||
if (needsCleanup) {
|
||||
this.elements.collectionHistory = null;
|
||||
}
|
||||
},
|
||||
|
||||
onhistorychecked() {
|
||||
debug('onhistorychecked');
|
||||
this.oncollectionchecked(this.elements.collectionHistory,
|
||||
HISTORY_SETTING);
|
||||
},
|
||||
|
||||
oncollectionchecked(element, setting) {
|
||||
if (!element || !setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
var checked = element.checked;
|
||||
var settingObj = {};
|
||||
settingObj[setting] = checked;
|
||||
if (!checked) {
|
||||
this.collections.delete(setting);
|
||||
navigator.mozSettings.createLock().set(settingObj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Yeah, mozId sucks...
|
||||
// We cannot call watch twice to change the id lifetime
|
||||
// callbacks. But we still need to do different stuff
|
||||
// within the onlogin callback depending on who calls
|
||||
// oncollectionchecked. So we need to save state.
|
||||
// Thank you mozId.
|
||||
this.setting = setting;
|
||||
this.settingObj = settingObj;
|
||||
|
||||
try {
|
||||
navigator.mozId.watch({
|
||||
wantIssuer: 'firefox-accounts',
|
||||
onlogin: () => {
|
||||
// This callback is fired every time a new account logs in,
|
||||
// but we only want to enable the history setting when this
|
||||
// callback is called because the refresh auth flow succeeded.
|
||||
// This can happen only if Sync is already enabled.
|
||||
if (this.state !== 'enabled') {
|
||||
return;
|
||||
}
|
||||
this.collections.set(this.setting, true);
|
||||
navigator.mozSettings.createLock().set(this.settingObj);
|
||||
},
|
||||
onlogout: () => {},
|
||||
onready: () => {},
|
||||
onerror: error => {
|
||||
element.checked = false;
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
} catch(e) {}
|
||||
|
||||
navigator.mozId.request({
|
||||
oncancel: () => {
|
||||
element.checked = false;
|
||||
},
|
||||
// We keep authenticated sessions of 5 minutes, if the user clicks on
|
||||
// the history collection switch after the 5 minutes are expired, we
|
||||
// will be asking her to re-enter her fxa password.
|
||||
refreshAuthentication: 5 * 60
|
||||
});
|
||||
},
|
||||
|
||||
addListener(element, event, listenerName) {
|
||||
if (!element || !event || !listenerName) {
|
||||
return;
|
||||
}
|
||||
// Because we bind this to the listener, we need to save
|
||||
// the reference so we can remove it afterwards.
|
||||
var listener = this[listenerName].bind(this);
|
||||
element.addEventListener(event, listener);
|
||||
return listener;
|
||||
},
|
||||
|
||||
removeListener(element, event, listener) {
|
||||
if (!element || !event || !listener) {
|
||||
return;
|
||||
}
|
||||
element.removeEventListener(event, listener);
|
||||
},
|
||||
|
||||
loadElements(screen) {
|
||||
ELEMENTS.forEach(element => {
|
||||
var name = getElementName(element.selector);
|
||||
if (!Array.isArray(element.screen)) {
|
||||
element.screen = [element.screen];
|
||||
}
|
||||
if (!this.elements[name]) {
|
||||
this.elements[name] = this.area.querySelector(element.selector);
|
||||
this.listeners.set(
|
||||
name,
|
||||
this.addListener(this.elements[name],
|
||||
element.event, element.listener)
|
||||
);
|
||||
if (element.init) {
|
||||
this[element.init]();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.init) {
|
||||
this[element.init]();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.listeners.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.elements[name].checked) {
|
||||
this.elements[name].checked = false;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
showScreen(screen) {
|
||||
if (this.screen == screen) {
|
||||
return;
|
||||
}
|
||||
this.screen = screen;
|
||||
|
||||
this.loadElements(screen);
|
||||
|
||||
this.elements.enabled.hidden = (screen != ENABLED);
|
||||
this.elements.disabled.hidden = (screen != DISABLED);
|
||||
},
|
||||
|
||||
showUser(user) {
|
||||
navigator.mozL10n.setAttributes(this.elements.signedInAs,
|
||||
'fxsync-signed-in-as', {
|
||||
email: user
|
||||
});
|
||||
},
|
||||
|
||||
maybeEnableSyncNow() {
|
||||
if (!this.elements.syncNowButton) {
|
||||
return;
|
||||
}
|
||||
(this.collections.size <= 0) ?
|
||||
this.elements.syncNowButton.classList.add('disabled') :
|
||||
this.elements.syncNowButton.classList.remove('disabled');
|
||||
},
|
||||
|
||||
disableSyncNowAndCollections(disabled) {
|
||||
['collectionBookmarks',
|
||||
'collectionHistory'].forEach(name => {
|
||||
if (!this.elements[name]) {
|
||||
return;
|
||||
}
|
||||
this.elements[name].disabled = disabled;
|
||||
});
|
||||
disabled ? this.elements.syncNowButton.classList.add('disabled')
|
||||
: this.elements.syncNowButton.classList.remove('disabled');
|
||||
},
|
||||
|
||||
showEnabling() {
|
||||
this.elements.signInButton.classList.add('disabled');
|
||||
this.elements.signInButton.dataset.l10nId = 'fxsync-signing';
|
||||
},
|
||||
|
||||
hideEnabling() {
|
||||
this.elements.signInButton.classList.remove('disabled');
|
||||
this.elements.signInButton.dataset.l10nId = 'fxsync-sign-in';
|
||||
},
|
||||
|
||||
showSyncNow() {
|
||||
this.elements.syncNowButton.dataset.l10nId = 'fxsync-sync-now';
|
||||
this.disableSyncNowAndCollections(false);
|
||||
this.maybeEnableSyncNow();
|
||||
},
|
||||
|
||||
showSyncing() {
|
||||
this.elements.syncNowButton.dataset.l10nId = 'fxsync-syncing';
|
||||
this.disableSyncNowAndCollections(true);
|
||||
},
|
||||
|
||||
enable() {
|
||||
SyncManagerBridge.enable();
|
||||
},
|
||||
|
||||
disable() {
|
||||
BrowserDialog.createDialog('signout_confirm').then(() => {
|
||||
SyncManagerBridge.disable();
|
||||
});
|
||||
},
|
||||
|
||||
sync() {
|
||||
SyncManagerBridge.sync();
|
||||
},
|
||||
|
||||
openTos() {
|
||||
this.showWebpage('https://accounts.firefox.com/legal/terms');
|
||||
},
|
||||
|
||||
openPrivacy() {
|
||||
this.showWebpage('https://accounts.firefox.com/legal/privacy');
|
||||
},
|
||||
|
||||
showWebpage(url) {
|
||||
Awesomescreen.createAddNewTab();
|
||||
Browser.navigate(url);
|
||||
Settings.hide();
|
||||
}
|
||||
};
|
||||
|
||||
exports.FirefoxSyncSettings = FirefoxSyncSettings;
|
||||
|
||||
LazyLoader.load('js/sync/manager_bridge.js', () => {
|
||||
FirefoxSyncSettings.init();
|
||||
});
|
||||
}(window));
|
||||
113
gaia/tv_apps/browser/js/sync/toolbar.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* exports FirefoxSyncToolbar */
|
||||
|
||||
/* global LazyLoader */
|
||||
/* global Settings */
|
||||
/* global SyncManagerBridge */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This small module handles the logic of the Sync entry in the browser's
|
||||
* top right toolbar menu.
|
||||
*
|
||||
* We show two different variations of the UI depending on the state of Sync:
|
||||
*
|
||||
* - When Sync is disabled, we show a 'Sign in to Sync' entry. Clicking it
|
||||
* triggers the Firefox Accounts flow and signs the user into Sync if the
|
||||
* FxA flow is completed successfuly.
|
||||
* - When Sync is enabled, we show the 'Sign is as {{email}}' entry. Where
|
||||
* {{email}} is the email used by the user to login into FxA.
|
||||
*
|
||||
* We also handle a third Sync state. When an error is received through the
|
||||
* Sync Manager Bridge, we inform the user about them. The expected errors are:
|
||||
*
|
||||
* - ERROR_OFFLINE: The user is trying to log in Sync when the device is
|
||||
* offline.
|
||||
* - ERROR_INVALID_SYNC_ACCOUNT: The user is trying to log into Sync with a
|
||||
* Firefox Account that has never been used before with Sync. On the first
|
||||
* version we don't support the creation of new Sync accounts, so we ask the
|
||||
* user to first login in Firefox Desktop or Android and try login again
|
||||
* in the TV.
|
||||
* - ERROR_TRY_LATER: The server is probably down. We inform the user about it
|
||||
* and ask her to retry in a few minutes.
|
||||
* - ERROR_UNKNOWN: We don't know what the h* was wrong, but we can't log the
|
||||
* user in.
|
||||
*
|
||||
* Here we don't need to worry about keeping any kind of state or managing
|
||||
* any state transistion apart from the UI changes. The Sync State Machine
|
||||
* and Sync State Manager takes care of handling all that for us.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
const DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
console.log('[FirefoxSyncToolbar] ' + Array.slice(arguments).concat());
|
||||
}
|
||||
}
|
||||
|
||||
var FirefoxSyncToolbar = {
|
||||
init() {
|
||||
SyncManagerBridge.addListener(this.onsyncchange.bind(this));
|
||||
|
||||
this.syncBlock = document.getElementById('fxsync-block');
|
||||
this.syncTab = this.syncBlock.querySelector('#fxsync-tab');
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
refresh() {
|
||||
SyncManagerBridge.getInfo().then(this.onsyncchange.bind(this));
|
||||
},
|
||||
|
||||
onsyncchange(message) {
|
||||
debug(JSON.stringify(message));
|
||||
if (!message || !message.state) {
|
||||
throw new Error('Missing sync state');
|
||||
}
|
||||
switch (message.state) {
|
||||
case 'disabled':
|
||||
if (this.showListener) {
|
||||
this.syncBlock.removeEventListener('mouseup', this.showListener);
|
||||
this.showListener = null;
|
||||
}
|
||||
this.syncBlock.addEventListener('mouseup', SyncManagerBridge.enable);
|
||||
this.syncTab.setAttribute('data-l10n-id', 'fxsync-sign-in-to-sync');
|
||||
this.syncTab.removeAttribute('data-l10n-args');
|
||||
break;
|
||||
case 'enabling':
|
||||
Settings.show();
|
||||
this.syncTab.setAttribute('data-l10n-id', 'fxsync-signing');
|
||||
break;
|
||||
case 'syncing':
|
||||
case 'enabled':
|
||||
this.showListener = Settings.show.bind(Settings);
|
||||
this.syncBlock.addEventListener('mouseup', this.showListener);
|
||||
this.syncBlock.removeEventListener('mouseup',
|
||||
SyncManagerBridge.enable);
|
||||
navigator.mozL10n.setAttributes(this.syncTab, 'fxsync-signed-in-as', {
|
||||
email: message.user
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Exported only for testing purposes
|
||||
exports.FirefoxSyncToolbar = FirefoxSyncToolbar;
|
||||
|
||||
LazyLoader.load('js/sync/manager_bridge.js', () => {
|
||||
FirefoxSyncToolbar.init();
|
||||
});
|
||||
|
||||
}(window));
|
||||
758
gaia/tv_apps/browser/js/toolbar.js
Executable file
|
|
@ -0,0 +1,758 @@
|
|||
/* global Awesomescreen */
|
||||
/* global Browser */
|
||||
/* global BrowserDB */
|
||||
/* global LazyLoader */
|
||||
/* global SearchResult */
|
||||
/* global SearchUtil */
|
||||
/* global Settings */
|
||||
/* global Tooltip*/
|
||||
/* global UrlHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Browser app toolbar
|
||||
* @namespace Toolbar
|
||||
*/
|
||||
var Toolbar = {
|
||||
|
||||
/* screen size */
|
||||
SCREEN_HEIGHT: 1080,
|
||||
SCREEN_WIDTH: 1920,
|
||||
|
||||
/* zoom */
|
||||
availableZoomScale: [
|
||||
{'scale': 1.25, 'disp': '100%'},
|
||||
{'scale': 1.625, 'disp': '130%'},
|
||||
{'scale': 2.0, 'disp': '160%'},
|
||||
],
|
||||
defaultZoomScale: 0,
|
||||
zoomScale: 0,
|
||||
availableZoomScaleYoutube: [
|
||||
{'scale': 1.55, 'disp': '100%'},
|
||||
{'scale': 1.775, 'disp': '130%'},
|
||||
{'scale': 2.0, 'disp': '160%'},
|
||||
],
|
||||
|
||||
urlButtonMode: null,
|
||||
GO: 0,
|
||||
REFRESH: 1,
|
||||
STOP: 2,
|
||||
SEARCH: 3,
|
||||
|
||||
initStart: true,
|
||||
|
||||
/**
|
||||
* Intialise toolbar.
|
||||
*/
|
||||
init: function toolbar_init() {
|
||||
/* get Element */
|
||||
this.getAllElements();
|
||||
|
||||
/* Event Listener */
|
||||
window.addEventListener('click', this);
|
||||
|
||||
this.sidebarButtonBlock.addEventListener('mouseup',
|
||||
this.showHideSidebar.bind(this));
|
||||
|
||||
this.backButtonBlock.addEventListener('mouseup',
|
||||
this.goBack.bind(this));
|
||||
|
||||
this.forwardButtonBlock.addEventListener('mouseup',
|
||||
this.goForward.bind(this));
|
||||
|
||||
this.addressForm.addEventListener('submit',
|
||||
this.handleUrlFormSubmit.bind(this));
|
||||
|
||||
this.urlInput.addEventListener('input',
|
||||
this.handleUrlInputKeypress.bind(this));
|
||||
|
||||
this.addressButton.addEventListener('mouseup',
|
||||
this.clickAddressButton.bind(this));
|
||||
|
||||
this.searchForm.addEventListener('submit',
|
||||
this.handleSearchFormSubmit.bind(this));
|
||||
|
||||
this.searchInput.addEventListener('focus',
|
||||
this.handleSearchInputFocus.bind(this));
|
||||
|
||||
// this.searchInput.addEventListener('keyup',
|
||||
// this.handleSearchInputKeypress.bind(this));
|
||||
|
||||
this.searchInput.addEventListener('input',
|
||||
this.handleSearchInputInput.bind(this));
|
||||
|
||||
this.searchButton.addEventListener('mouseup',
|
||||
this.clickSearchButton.bind(this));
|
||||
|
||||
this.searchClearButton.addEventListener('mouseup',
|
||||
this.clickSearchClearButton.bind(this));
|
||||
|
||||
this.searchCloseButton.addEventListener('mouseup',
|
||||
this.clickSearchCloseButton.bind(this));
|
||||
|
||||
this.bookmarkButtonBlock.addEventListener('mouseup',
|
||||
Browser.addBookmark.bind(Browser));
|
||||
this.bookmarkButtonAnime.addEventListener('animationend',
|
||||
Browser.bookmarkButtonAnimeEnd.bind(Browser));
|
||||
|
||||
this.showBookmarksButtonBlock.addEventListener('mouseup',
|
||||
Awesomescreen.selectBookmarksTab.bind(Awesomescreen));
|
||||
|
||||
this.homeButtonBlock.addEventListener('mouseup',
|
||||
this.clickHomeButtonBlock.bind(this));
|
||||
|
||||
this.zoomButtonBlock.addEventListener('mouseup',
|
||||
this.clickZoomButtonBlock.bind(this));
|
||||
|
||||
this.tabsButtonBlock.addEventListener('click',
|
||||
Awesomescreen.handleNewTab.bind(Awesomescreen));
|
||||
|
||||
this.newTabButtonBlock.addEventListener('click',
|
||||
Awesomescreen.createAddNewTab.bind(Awesomescreen));
|
||||
|
||||
this.menuButtonBlock.addEventListener('mouseup',
|
||||
this.clickMenuButtonBlock.bind(this));
|
||||
this.historyBlock.addEventListener('mouseup',
|
||||
Awesomescreen.selectHistoryTab.bind(Awesomescreen));
|
||||
this.pocketBlock.addEventListener('mouseup',
|
||||
this.clickPocketListBlock.bind(this));
|
||||
this.privateWindowBlock.addEventListener('mouseup',
|
||||
Browser.handlePrivateBrowsing.bind(Browser));
|
||||
this.settingsBlock.addEventListener('mouseup',
|
||||
Settings.show.bind(Settings));
|
||||
|
||||
this.modeButtonBlock.addEventListener('mouseover',
|
||||
this.selectedModeButtonBlock.bind(this));
|
||||
this.modeButtonBlock.addEventListener('mouseout',
|
||||
this.unselectedModeButtonBlock.bind(this));
|
||||
|
||||
this.toolbarPanel.dataset.search = 'false';
|
||||
|
||||
var colorBar = Browser.getColorBarData();
|
||||
this.panCursorButtonBlock.dataset.colorbar = colorBar[0].COLOR;
|
||||
this.zoomButtonBlock.dataset.colorbar = colorBar[1].COLOR;
|
||||
this.tabsButtonBlock.dataset.colorbar = colorBar[2].COLOR;
|
||||
this.newTabButtonBlock.dataset.colorbar = colorBar[3].COLOR;
|
||||
|
||||
// li = 73px * 3(list) + 20px(hover) + 10px(padding)
|
||||
this.menuBlock.style.height = 73 * 3 + 20 + 10 + 'px';
|
||||
Tooltip.init();
|
||||
|
||||
//IFDEF_FIREFOX_SYNC
|
||||
LazyLoader.load('js/sync/toolbar.js');
|
||||
//ENDIF_FIREFOX_SYNC
|
||||
},
|
||||
|
||||
/**
|
||||
* toolbar bookmark registered check.
|
||||
*/
|
||||
isBookmarks: function toolbar_isBookmarks() {
|
||||
if(Toolbar.bookmarkButton.dataset.isbookmark == 'true'){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set search engine
|
||||
*/
|
||||
setSearchEngine: function toolbar_setSearchEngine() {
|
||||
var engineName = SearchUtil.getCurrentEngineName().toLowerCase();
|
||||
// favicon
|
||||
this.searchFavicon.dataset.engine = engineName;
|
||||
// placeholder
|
||||
engineName = engineName.charAt(0).toUpperCase() + engineName.slice(1);
|
||||
this.searchInput.placeholder = engineName;
|
||||
// tooltip
|
||||
navigator.mozL10n.formatValue('WB_LT_TIPS_SEARCH_BAR2', {
|
||||
engine: engineName
|
||||
}).then(val => {
|
||||
this.searchInput.dataset.tips = val;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* CamelCase (xxx-yyy -> xxxYyy)
|
||||
*/
|
||||
toCamelCase: function toCamelCase(str) {
|
||||
return str.replace(/-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get All Elements (from id)
|
||||
*/
|
||||
getAllElements: function toolbar_getAllElements() {
|
||||
var elementIDs = [
|
||||
'toolbar-panel',
|
||||
'sidebar-button-block', 'sidebar-button',
|
||||
'back-button-block', 'back-button',
|
||||
'forward-button-block', 'forward-button-border', 'forward-button',
|
||||
'space-block',
|
||||
'addressbar-block', 'address-form', 'ssl-indicator', 'url-input',
|
||||
'address-button',
|
||||
'searchbar-block', 'search-form', 'search-favicon', 'search-input',
|
||||
'search-button', 'search-clear-button', 'search-close-button',
|
||||
'iconbutton-block',
|
||||
'bookmark-button-block', 'bookmark-button',
|
||||
'show-bookmarks-button-block', 'show-bookmarks-button',
|
||||
'home-button-block', 'home-button',
|
||||
'zoom-button-block', 'zoom-button',
|
||||
'tabs-button-block', 'tabs-button',
|
||||
'new-tab-button-block', 'new-tab-button',
|
||||
'menu-button-block', 'menu-button',
|
||||
'menu-block',
|
||||
'history-block', 'private-window-block',
|
||||
'settings-block',
|
||||
'history-tab', 'private-window-tab', 'settings-tab',
|
||||
'mode-button-block',
|
||||
'mode-button-title', 'pan-cursor-button', 'pan-cursor-button-block',
|
||||
'pan-cursor-banner-message',
|
||||
'zoom-banner-message-block', 'zoom-banner-message',
|
||||
'loading-icon',
|
||||
//'tooltip-block',
|
||||
'bookmark-button-anime', 'show-bookmarks-button-anime',
|
||||
'pocket-block', 'pocket-tab'
|
||||
];
|
||||
|
||||
// Loop and add element with camel style name to Modal Dialog attribute.
|
||||
elementIDs.forEach(function createElementRef(name) {
|
||||
this[this.toCamelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* handle event
|
||||
*/
|
||||
handleEvent: function toolbar_handleEvent(ev) {
|
||||
switch(ev.type) {
|
||||
case 'click':
|
||||
if(ev.target != this.menuButtonBlock) {
|
||||
this.toolbarPanel.dataset.menu = 'hide';
|
||||
}
|
||||
|
||||
// Settings in the display
|
||||
if(Settings.isDisplayed()) {
|
||||
switch(ev.target) {
|
||||
case this.backButtonBlock:
|
||||
case this.forwardButtonBlock:
|
||||
case this.addressForm:
|
||||
case this.urlInput:
|
||||
case this.addressButton:
|
||||
case this.searchForm:
|
||||
case this.searchInput:
|
||||
case this.searchButton:
|
||||
case this.bookmarkButtonBlock:
|
||||
case this.showBookmarksButtonBlock:
|
||||
case this.homeButtonBlock:
|
||||
case this.zoomButtonBlock:
|
||||
case this.tabsButtonBlock:
|
||||
case this.newTabButtonBlock:
|
||||
case this.menuButtonBlock:
|
||||
Settings.hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Specialized processing youtube (zoom)
|
||||
*/
|
||||
checkYoutube: function toolbar_checkYoutube() {
|
||||
if(!Browser.currentInfo || !Browser.currentInfo.url) {
|
||||
return false;
|
||||
}
|
||||
var end_pos1 =
|
||||
Browser.currentInfo.url.indexOf('https://www.youtube.com', 0);
|
||||
var end_pos2 = Browser.currentInfo.url.indexOf('http://www.youtube.com', 0);
|
||||
if(end_pos1 < 0 && end_pos2 < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Full screen or Screen with submenu
|
||||
*/
|
||||
showHideSidebar: function toolbar_showHideSidebar() {
|
||||
this.sidebarButtonBlock.blur();
|
||||
// Hide Tooltip
|
||||
Tooltip.hide();
|
||||
|
||||
// Fade animation end event handler
|
||||
var fade_event = (function() {
|
||||
Browser.fadeBase.classList.toggle('fade');
|
||||
if(Browser.sideBlock.dataset.sidebar == 'true'){
|
||||
// Full screen
|
||||
Browser.sideBlock.dataset.sidebar = 'false';
|
||||
Browser.mainBlock.dataset.sidebar = 'false';
|
||||
// save screen mode
|
||||
BrowserDB.updateSetting('full', 'screen_mode');
|
||||
}else{
|
||||
// Disp side screen
|
||||
Browser.sideBlock.dataset.sidebar = 'true';
|
||||
Browser.mainBlock.dataset.sidebar = 'true';
|
||||
// save screen mode
|
||||
BrowserDB.updateSetting('side', 'screen_mode');
|
||||
}
|
||||
Browser.fadeBase.removeEventListener('transitionend', fade_event, false);
|
||||
// Specialized processing youtube (zoom)
|
||||
Browser.currentInfo.dom.zoom(Toolbar.getZoomScale());
|
||||
});
|
||||
// Add fade animantion event
|
||||
Browser.fadeBase.addEventListener('transitionend', fade_event, false);
|
||||
Browser.fadeBase.classList.toggle('fade');
|
||||
this.sidebarButtonBlock.dataset.fade='true';
|
||||
},
|
||||
|
||||
/**
|
||||
* Go back to the previous page
|
||||
*/
|
||||
goBack: function toolbar_goBack() {
|
||||
|
||||
if (Awesomescreen.isDisplayedTop()){
|
||||
Awesomescreen.topsiteReturnFunc();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Browser.currentInfo.dom.getCanGoBack){
|
||||
return;
|
||||
}
|
||||
|
||||
Browser.currentInfo.dom.getCanGoBack().onsuccess = (function(ev) {
|
||||
if(!ev.target.result){
|
||||
// dialog
|
||||
// XXX: refactor this and the same code in awesomescreen.js
|
||||
navigator.mozL10n.formatValue('LT_BROWSER_CONFIRM_EXIT2')
|
||||
.then(result => {
|
||||
if (window.confirm(result)) {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
Browser.currentInfo.dom.goBack();
|
||||
},
|
||||
|
||||
/**
|
||||
* Go forward to the next page
|
||||
*/
|
||||
goForward: function toolbar_goForward() {
|
||||
Browser.currentInfo.dom.goForward();
|
||||
},
|
||||
|
||||
// Set url form input
|
||||
setUrlBar: function toolbar_setUrlBar(value) {
|
||||
this.urlInput.value = value;
|
||||
},
|
||||
// Get url form input
|
||||
getUrlFromInput: function toolbar_getUrlFromInput(input) {
|
||||
var hasScheme = UrlHelper.hasScheme(input);
|
||||
|
||||
// No scheme, prepend basic protocol and return
|
||||
if (!hasScheme) {
|
||||
return 'http://' + input;
|
||||
}
|
||||
|
||||
return input;
|
||||
},
|
||||
/**
|
||||
* Url form submit
|
||||
*/
|
||||
handleUrlFormSubmit: function toolbar_handleUrlFormSubmit(ev) {
|
||||
if (ev) {
|
||||
// Can be canceled
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
if (this.urlButtonMode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = this.getUrlFromInput(this.urlInput.value);
|
||||
|
||||
if (url !== Browser.currentInfo.url) {
|
||||
this.setUrlBar(url);
|
||||
Browser.currentInfo.url = url;
|
||||
}
|
||||
|
||||
this.urlInput.blur();
|
||||
|
||||
Browser.navigate(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Input Url
|
||||
*/
|
||||
handleUrlInputKeypress: function toolbar_handleUrlInputKeypress(evt) {
|
||||
var input = this.urlInput.value;
|
||||
if (input === '') {
|
||||
this.setUrlButtonMode(null);
|
||||
this.addressButton.classList.add('disable');
|
||||
return;
|
||||
}
|
||||
|
||||
this.addressButton.classList.remove('disable');
|
||||
this.setUrlButtonMode(
|
||||
UrlHelper.isNotURL(input) ? this.SEARCH : this.GO
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Address Bookmark Current url
|
||||
*/
|
||||
clickAddressButton: function toolbar_clickAddressButton(ev) {
|
||||
//if(this.addressButton.classList.contains('disable')){
|
||||
//return;
|
||||
//}
|
||||
if(this.addressButton.dataset.mode == 'load') {
|
||||
if(Browser.currentInfo.url){
|
||||
Browser.currentInfo.dom.reload(false);
|
||||
}
|
||||
this.stopAddressIcon();
|
||||
} else {
|
||||
Browser.currentInfo.dom.stop();
|
||||
this.hiddenLoadingIcon();
|
||||
this.loadAddressIcon();
|
||||
}
|
||||
},
|
||||
|
||||
loadAddressIcon: function toolbar_loadAddressIcon() {
|
||||
this.addressButton.dataset.mode = 'load';
|
||||
this.addressButton.dataset.tips = 'WB_LT_TIPS_RELOAD';
|
||||
},
|
||||
|
||||
stopAddressIcon: function toolbar_stopAddressIcon() {
|
||||
this.addressButton.dataset.mode = 'stop';
|
||||
this.addressButton.dataset.tips = 'WB_LT_TIPS_STOP_LOADING';
|
||||
},
|
||||
|
||||
showLoadingIcon: function toolbar_showLoadingIcon() {
|
||||
if(Settings.isDisplayed()) {
|
||||
return;
|
||||
}
|
||||
if(!this.loadingIcon.classList.contains('visible')){
|
||||
this.loadingIcon.classList.add('visible');
|
||||
this.stopAddressIcon();
|
||||
}
|
||||
},
|
||||
|
||||
hiddenLoadingIcon: function toolbar_hiddenLoadingIcon() {
|
||||
if(Tooltip.isShow(this.addressButton)){
|
||||
Tooltip.hide();
|
||||
}
|
||||
this.loadingIcon.classList.remove('visible');
|
||||
this.loadAddressIcon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit search form
|
||||
*/
|
||||
handleSearchFormSubmit: function toolbar_handleSearchFormSubmit(ev) {
|
||||
if (ev) {
|
||||
// Can be canceled
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
if( SearchResult.isDisplayed() ) {
|
||||
this.searchInput.blur();
|
||||
SearchResult.hide();
|
||||
}
|
||||
var url = Browser.getSearchFromInput(this.searchInput.value);
|
||||
Browser.navigate(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Click search button
|
||||
*/
|
||||
clickSearchButton: function toolbar_clickSearchButton(ev) {
|
||||
this.handleSearchFormSubmit(ev);
|
||||
},
|
||||
|
||||
clickSearchClearButton: function toolbar_clickSearchClearButton(ev) {
|
||||
if( ev ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this.searchInput.value = '';
|
||||
SearchResult.resultUpdate(this.searchInput.value);
|
||||
},
|
||||
|
||||
clickSearchCloseButton: function toolbar_clickSearchCloseButton(ev) {
|
||||
if( ev ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
SearchResult.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Input Search
|
||||
*/
|
||||
getSearchBarStyle: function toolbar_getSearchBarStyle() {
|
||||
return this.toolbarPanel.dataset.search;
|
||||
},
|
||||
|
||||
setSearchBarStyle: function toolbar_setSearchBarStyle(value) {
|
||||
this.toolbarPanel.dataset.search = value;
|
||||
},
|
||||
|
||||
handleSearchInputFocus: function toolbar_handleSearchInputFocus(evt) {
|
||||
this.handleSearchInputKeypress(evt);
|
||||
},
|
||||
|
||||
handleSearchInputInput: function toolbar_handleSearchInputInput(evt) {
|
||||
this.handleSearchInputKeypress(evt);
|
||||
},
|
||||
|
||||
handleSearchInputKeypress: function toolbar_handleSearchInputKeypress(evt) {
|
||||
if( this.getSearchBarStyle() == 'false' ) {
|
||||
if( evt ) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if(Toolbar.searchInput.value) {
|
||||
Tooltip.hide();
|
||||
this.setSearchBarStyle('true');
|
||||
SearchResult.show();
|
||||
}
|
||||
} else {
|
||||
SearchResult.handleSearchResultFormInput(evt);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Click home button
|
||||
*/
|
||||
clickHomeButtonBlock: function toolbar_clickHomeButtonBlock(ev) {
|
||||
Settings.getHomepage((function(result) {
|
||||
if(result) {
|
||||
Browser.navigate(result);
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Click zoom button
|
||||
*/
|
||||
clickZoomButtonBlock: function toolbar_clickZoomButtonBlock(ev) {
|
||||
if(ev){
|
||||
// stop event
|
||||
ev.stopPropagation();
|
||||
}
|
||||
clearTimeout(this.showZoomBannerTimeoutID);
|
||||
|
||||
this.zoomScale = Browser.currentInfo.zoom;
|
||||
this.zoomScale++;
|
||||
if(this.zoomScale >= this.availableZoomScale.length) {
|
||||
this.zoomScale = 0;
|
||||
}
|
||||
Browser.currentInfo.zoom = this.zoomScale;
|
||||
Browser.currentInfo.dom.zoom(this.getZoomScale());
|
||||
|
||||
this.zoomBannerMessage.innerHTML =
|
||||
this.availableZoomScale[this.zoomScale].disp;
|
||||
if(!this.zoomBannerMessageBlock.classList.contains('visible')){
|
||||
this.zoomBannerMessageBlock.classList.add('visible');
|
||||
}
|
||||
this.showZoomBannerTimeoutID = setTimeout(function(){
|
||||
Toolbar.zoomBannerMessageBlock.classList.remove('visible');
|
||||
}, 1500);
|
||||
},
|
||||
getZoomScale: function toolbar_getZoomScale() {
|
||||
if(this.checkYoutube() && Browser.sideBlock.dataset.sidebar == 'false'){
|
||||
return this.availableZoomScaleYoutube[this.zoomScale].scale;
|
||||
} else {
|
||||
return this.availableZoomScale[this.zoomScale].scale;
|
||||
}
|
||||
},
|
||||
setZoomScale: function toolbar_setZoomScale(scale) {
|
||||
this.zoomScale = scale;
|
||||
},
|
||||
getDefaultZoomScale: function toolbar_getDefaultZoomScale() {
|
||||
if(this.checkYoutube() && Browser.sideBlock.dataset.sidebar == 'false'){
|
||||
return this.availableZoomScaleYoutube[this.defaultZoomScale].scale;
|
||||
} else {
|
||||
return this.availableZoomScale[this.defaultZoomScale].scale;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Zoom out(for ars)
|
||||
*/
|
||||
zoomOut: function toolbar_zoomOut() {
|
||||
clearTimeout(this.showZoomBannerTimeoutID);
|
||||
|
||||
this.zoomScale = Browser.currentInfo.zoom;
|
||||
if(this.zoomScale === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.zoomScale--;
|
||||
Browser.currentInfo.zoom = this.zoomScale;
|
||||
Browser.currentInfo.dom.zoom(this.getZoomScale());
|
||||
|
||||
this.zoomBannerMessage.innerHTML =
|
||||
this.availableZoomScale[this.zoomScale].disp;
|
||||
if(!this.zoomBannerMessageBlock.classList.contains('visible')){
|
||||
this.zoomBannerMessageBlock.classList.add('visible');
|
||||
}
|
||||
this.showZoomBannerTimeoutID = setTimeout(function(){
|
||||
Toolbar.zoomBannerMessageBlock.classList.remove('visible');
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Click menu button
|
||||
*/
|
||||
clickMenuButtonBlock: function toolbar_clickMenuButtonBlock(ev) {
|
||||
if(ev){
|
||||
// stop event
|
||||
ev.stopPropagation();
|
||||
}
|
||||
if(this.toolbarPanel.dataset.menu == 'show') {
|
||||
this.toolbarPanel.dataset.menu = 'hide';
|
||||
} else {
|
||||
this.toolbarPanel.dataset.menu = 'show';
|
||||
if(!this.menuBlock.style.minWidth) {
|
||||
var ar = [];
|
||||
// rate 1.25
|
||||
ar.push(432); // default 432(540 / 1.25)
|
||||
ar.push(this.historyTab.offsetWidth);
|
||||
ar.push(this.privateWindowTab.offsetWidth);
|
||||
|
||||
ar.push(this.settingsTab.offsetWidth);
|
||||
this.menuBlock.style.minWidth =
|
||||
(Math.max.apply(null, ar) * 1.25) + 'px';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show mode button
|
||||
*/
|
||||
showModeButtonTimer: function toolbar_showModeButtonTimer() {
|
||||
this.showModeButtonTimeoutID = setTimeout(function(){
|
||||
Toolbar.modeButtonBlock.classList.remove('visible');
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show mode button
|
||||
*/
|
||||
showPanCursorButton: function toolbar_showPanCursorButton() {
|
||||
if(!this.modeButtonBlock.classList.contains('visible')){
|
||||
this.modeButtonBlock.classList.add('visible');
|
||||
this.showModeButtonTimer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selected mode button
|
||||
*/
|
||||
selectedModeButtonBlock: function toolbar_selectedModeButtonBlock() {
|
||||
clearTimeout(this.showModeButtonTimeoutID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unselected mode button
|
||||
*/
|
||||
unselectedModeButtonBlock: function toolbar_unselectedModeButtonBlock() {
|
||||
this.showModeButtonTimer();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show pan cursor banner
|
||||
*/
|
||||
showPanCursorBanner: function toolbar_showPanCursorBanner() {
|
||||
if(!this.panCursorBannerMessage.classList.contains('visible')){
|
||||
this.panCursorBannerMessage.classList.add('visible');
|
||||
}
|
||||
clearTimeout(this.showPanCursorBannerTimeoutID);
|
||||
this.showPanCursorBannerTimeoutID = setTimeout(function(){
|
||||
Toolbar.panCursorBannerMessage.classList.remove('visible');
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Click mode button
|
||||
*/
|
||||
clickModeButtonBlock: function toolbar_clickModeButtonBlock() {
|
||||
if(Browser.getCursorPanMode() == 'cursor') {
|
||||
Browser.setCursorPanMode('pan');
|
||||
this.modeButtonTitle.setAttribute('data-l10n-id', 'WB_LT_PAN_MODE');
|
||||
this.panCursorBannerMessage.setAttribute(
|
||||
'data-l10n-id', 'WB_LT_SWITCH_PAN_MODE');
|
||||
} else {
|
||||
this.clearDragMode();
|
||||
}
|
||||
|
||||
// Show mode button
|
||||
this.showPanCursorButton();
|
||||
// Show pan cursor banner
|
||||
this.showPanCursorBanner();
|
||||
},
|
||||
// clear drag mode
|
||||
clearDragMode: function toolbar_clearDragMode(){
|
||||
if(Browser.getCursorPanMode() == 'pan') {
|
||||
Browser.setCursorPanMode('cursor');
|
||||
this.modeButtonTitle.setAttribute('data-l10n-id', 'WB_LT_CURSOR_MODE');
|
||||
this.panCursorBannerMessage.setAttribute(
|
||||
'data-l10n-id', 'WB_LT_SWITCH_CURSOR_MODE');
|
||||
}
|
||||
},
|
||||
|
||||
setUrlButtonMode: function toolbar_setUrlButtonMode(mode) {
|
||||
this.urlButtonMode = mode;
|
||||
},
|
||||
|
||||
setPrivateBrowsing: function toolbar_setPrivateBrowsing(bool) {
|
||||
this.toolbarPanel.dataset.pvt = bool;
|
||||
},
|
||||
|
||||
isPrivateBrowsing: function toolbar_getPrivateBrowsing() {
|
||||
return (this.toolbarPanel.dataset.pvt == 'true');
|
||||
},
|
||||
|
||||
initStartBrowsing: function toolbar_initStartBrowsing() {
|
||||
if(this.initStart) {
|
||||
this.initStart = false;
|
||||
/* show pan cursor mode button */
|
||||
this.showPanCursorButton();
|
||||
}
|
||||
},
|
||||
|
||||
setToolbarMode: function toolbar_setToolbarMode(mode) {
|
||||
this.toolbarPanel.dataset.mode = mode;
|
||||
},
|
||||
getToolbarMode: function toolbar_getToolbarMode() {
|
||||
return this.toolbarPanel.dataset.mode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open Pocket List
|
||||
*/
|
||||
clickPocketListBlock: function toolbar_clickPocketListBlock() {
|
||||
var pocketHomePageUri = 'https://getpocket.com/fxostv';
|
||||
if (Browser.currentInfo.url &&
|
||||
UrlHelper.isURL(Browser.currentInfo.url)) {
|
||||
var ev = {
|
||||
detail: {
|
||||
url: pocketHomePageUri,
|
||||
frameElement: null
|
||||
}
|
||||
};
|
||||
Awesomescreen.openNewTab(ev);
|
||||
} else {
|
||||
Browser.navigate(pocketHomePageUri);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
190
gaia/tv_apps/browser/js/tooltip.js
Executable file
|
|
@ -0,0 +1,190 @@
|
|||
/* global _ */
|
||||
/* global Toolbar */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Browser App Tooltip.
|
||||
* @namespace Tooltip
|
||||
*/
|
||||
var Tooltip = {
|
||||
tooltipBlock: null,
|
||||
showTimeoutID: null,
|
||||
hideTimeoutID: null,
|
||||
hideTimeoutData: 100,
|
||||
overObj: null,
|
||||
moveObj: null,
|
||||
showTarget: null,
|
||||
|
||||
/**
|
||||
* initialize
|
||||
*/
|
||||
init: function tooltip_init() {
|
||||
this.tooltipBlock = document.getElementById('tooltip-block');
|
||||
|
||||
var elements = [
|
||||
{target: Toolbar.sidebarButtonBlock},
|
||||
{target: Toolbar.backButtonBlock},
|
||||
{target: Toolbar.forwardButtonBlock},
|
||||
{target: Toolbar.addressButton},
|
||||
{target: Toolbar.searchInput},
|
||||
{target: Toolbar.bookmarkButtonBlock},
|
||||
{target: Toolbar.showBookmarksButtonBlock},
|
||||
{target: Toolbar.homeButtonBlock},
|
||||
{target: Toolbar.zoomButtonBlock},
|
||||
{target: Toolbar.tabsButtonBlock},
|
||||
{target: Toolbar.newTabButtonBlock},
|
||||
{target: Toolbar.menuButtonBlock},
|
||||
{target: Toolbar.modeButtonBlock},
|
||||
];
|
||||
|
||||
this.overObj = this.mouseoverEvent.bind(this);
|
||||
this.moveObj = this.mousemoveEvent.bind(this);
|
||||
// toolbar icon
|
||||
elements.forEach(function addEvent(elm) {
|
||||
var target = elm.target;
|
||||
// add event
|
||||
target.addEventListener('mouseover', this.overObj);
|
||||
target.addEventListener('mouseout', this.mouseoutEvent.bind(this));
|
||||
target.addEventListener('mousedown', this.clickEvent.bind(this));
|
||||
target.addEventListener('mouseup', this.clickEvent.bind(this));
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* mousemove event
|
||||
*/
|
||||
mousemoveEvent: function tooltip_mousemoveEvent(ev) {
|
||||
if(this.isShow()) {
|
||||
// hide the tooltip in mouse movement at tooltip display.
|
||||
this.stopMousemoveEvent(ev.target);
|
||||
this.hideTimeoutID = setTimeout(function(){
|
||||
Tooltip.hide();
|
||||
}, this.hideTimeoutData);
|
||||
return;
|
||||
}
|
||||
if(this.showTimeoutID) {
|
||||
clearTimeout(this.showTimeoutID);
|
||||
}
|
||||
|
||||
var bounds = ev.currentTarget.getBoundingClientRect();
|
||||
var posY = Math.ceil(bounds.bottom);
|
||||
var top = Math.ceil(bounds.top);
|
||||
var tips = null;
|
||||
if(ev.target.dataset.tips != null && ev.target.dataset.tips != undefined) {
|
||||
if(ev.target == Toolbar.searchInput) {
|
||||
tips = {raw: ev.target.dataset.tips};
|
||||
} else {
|
||||
tips = ev.target.dataset.tips;
|
||||
}
|
||||
}
|
||||
this.showTimeoutID = setTimeout(function(){
|
||||
Tooltip.showTarget = ev.target;
|
||||
var disp = {
|
||||
top: top,
|
||||
text: tips,
|
||||
x: ev.pageX,
|
||||
//y: ev.pageY
|
||||
y: posY,
|
||||
};
|
||||
Tooltip.displaySetting(disp);
|
||||
}, 150);
|
||||
},
|
||||
startMousemoveEvent: function tooltip_startMousemoveEvent(target) {
|
||||
target.addEventListener('mousemove', this.moveObj);
|
||||
},
|
||||
stopMousemoveEvent: function tooltip_stopMousemoveEvent(target) {
|
||||
target.removeEventListener('mousemove', this.moveObj);
|
||||
},
|
||||
|
||||
/**
|
||||
* mouseover event
|
||||
*/
|
||||
mouseoverEvent: function tooltip_mouseoverEvent(ev) {
|
||||
this.startMousemoveEvent(ev.target);
|
||||
},
|
||||
|
||||
/**
|
||||
* mouseout event
|
||||
*/
|
||||
mouseoutEvent: function tooltip_mouseoutEvent(ev) {
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* click event
|
||||
*/
|
||||
clickEvent: function tooltip_clickEvent(ev) {
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* show tooltip
|
||||
*/
|
||||
isShow: function tooltip_isShow(target) {
|
||||
if(!target){
|
||||
return this.tooltipBlock.classList.contains('visible');
|
||||
} else {
|
||||
if(target == this.showTarget){
|
||||
return this.tooltipBlock.classList.contains('visible');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* show tooltip
|
||||
*/
|
||||
show: function tooltip_show() {
|
||||
this.tooltipBlock.classList.add('visible');
|
||||
},
|
||||
|
||||
/**
|
||||
* hide tooltip
|
||||
*/
|
||||
hide: function tooltip_hide() {
|
||||
clearTimeout(this.showTimeoutID);
|
||||
clearTimeout(this.hideTimeoutID);
|
||||
this.tooltipBlock.classList.remove('visible');
|
||||
},
|
||||
|
||||
/**
|
||||
* display setting
|
||||
*/
|
||||
displaySetting: function tooltip_displaySetting(disp) {
|
||||
if(disp.text == null || disp.text == undefined) {
|
||||
return;
|
||||
}
|
||||
this.show();
|
||||
this.tooltipBlock.style.left = '0px';
|
||||
if (typeof disp.text === 'string') {
|
||||
this.tooltipBlock.setAttribute('data-l10n-id', disp.text);
|
||||
} else if (disp.text.hasOwnProperty('raw')) {
|
||||
this.tooltipBlock.innerHTML = disp.text.replace(/\\n/g, '<br>');
|
||||
}
|
||||
|
||||
// pos x adjustment
|
||||
// 25 = scrollbar margin
|
||||
var maxW = this.tooltipBlock.offsetWidth + disp.x + 25;
|
||||
var posX = disp.x;
|
||||
if(maxW >= Browser.SCREEN_WIDTH) {
|
||||
// 58 = margin
|
||||
posX = Browser.SCREEN_WIDTH - this.tooltipBlock.offsetWidth - 58;
|
||||
}
|
||||
|
||||
// pos y adjustment
|
||||
var bounds = this.tooltipBlock.getBoundingClientRect();
|
||||
var hg = Math.ceil(bounds.height);
|
||||
var posY = disp.y + 20;
|
||||
var maxH = posY + hg;
|
||||
if(maxH >= Browser.SCREEN_HEIGHT) {
|
||||
// hover item
|
||||
posY = disp.top - 20 - hg;
|
||||
}
|
||||
|
||||
this.tooltipBlock.style.top = (posY + 0) + 'px';
|
||||
this.tooltipBlock.style.left = (posX + 0) + 'px';
|
||||
},
|
||||
};
|
||||
|
||||