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.
|
|
@ -0,0 +1 @@
|
|||
;window.COMPONENTS_BASE_URL='/shared/elements/';
|
||||
1686
assets/webapps/verticalhome.gaiamobile.org/shared/elements/gaia-header/dist/gaia-header.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* Generated by grunt-webfont */
|
||||
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
|
||||
|
||||
@font-face {
|
||||
font-family: "gaia-icons";
|
||||
src: url("fonts/gaia-icons.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[data-icon]:before,
|
||||
.ligature-icons {
|
||||
font-family: "gaia-icons";
|
||||
content: attr(data-icon);
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-size: 30px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
|
||||
/* Highlight cases where icons are not localized and still visible to screen
|
||||
reader */
|
||||
background: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
width: 20rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
|
||||
z-index: 9999;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 18.2995,19.66 -5.056,-5.057 5.056,-5.056 c -0.31,-0.516 -0.62,-1.032 -0.93,-1.34 l -5.672,5.673 v 1.652 l 5.675,5.676 c 0.31,-0.515 0.722,-0.928 0.93,-1.547 z"/></svg>
|
||||
|
After Width: | Height: | Size: 294 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="M11.8,19.9l4.9-4.9l-4.9-4.9c0.3-0.5,0.6-1,0.9-1.3l5.5,5.5c0,0.3,0,0.5,0,0.8c0,0.3,0,0.5,0,0.8l-5.5,5.5 C12.4,20.9,12,20.5,11.8,19.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 243 B |
|
|
@ -0,0 +1,18 @@
|
|||
'use strict';
|
||||
/* global ComponentUtils */
|
||||
|
||||
window.GaiaButtons = (function(win) {
|
||||
// Extend from the HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
// Allow baseurl to be overridden (used for demo page)
|
||||
var baseurl = window.GaiaButtonsBaseurl ||
|
||||
'/shared/elements/gaia_buttons/';
|
||||
|
||||
proto.createdCallback = function() {
|
||||
ComponentUtils.style.call(this, baseurl);
|
||||
};
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('gaia-buttons', { prototype: proto });
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
/* ---------------------------------------
|
||||
* gaia-buttons:
|
||||
* Container for buttons e.g:
|
||||
* action menu, confirm, edit mode, FTU
|
||||
* --------------------------------------- */
|
||||
|
||||
gaia-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 7rem;
|
||||
box-sizing: border-box;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
gaia-buttons:not(.inline) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
border-top: 0.1rem solid #e7e7e7;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
/* this 'end' padding must match the buttons 'start' margin */
|
||||
gaia-buttons:not(.inline):-moz-dir(ltr) { padding-right: 1.5rem; }
|
||||
gaia-buttons:not(.inline):-moz-dir(rtl) { padding-left: 1.5rem; }
|
||||
|
||||
gaia-buttons.inline a,
|
||||
gaia-buttons.inline button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
position: relative;
|
||||
font-family: sans-serif;
|
||||
font-style: italic;
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
margin: 0;
|
||||
padding: 0 0.2rem;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
background: #d8d8d8;
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
font-weight: normal;
|
||||
font-size: 1.6rem;
|
||||
line-height: 4rem;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
:-moz-dir(ltr) a,
|
||||
:-moz-dir(ltr) button {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
:-moz-dir(rtl) a,
|
||||
:-moz-dir(rtl) button {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
/* Recommend */
|
||||
a.recommend,
|
||||
button.recommend {
|
||||
background-color: #00caf2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Danger */
|
||||
a.danger,
|
||||
button.danger {
|
||||
background-color: #e51e1e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Pressed */
|
||||
a:active,
|
||||
button:active {
|
||||
background-color: #b2f2ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a::-moz-selection,
|
||||
button::-moz-selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
a[aria-disabled="true"],
|
||||
button[disabled] {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
color: #c7c7c7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
a[aria-disabled="true"].recommend,
|
||||
button[disabled].recommend {
|
||||
background-color: #97cbd5;
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
a[aria-disabled="true"].danger,
|
||||
button[disabled].danger {
|
||||
background-color: #eb7474;
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
a[aria-disabled="true"]::-moz-selection,
|
||||
button[disabled]::-moz-selection {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/* Hidden */
|
||||
[hidden] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
a.icon,
|
||||
button.icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a.icon:before,
|
||||
button.icon:before {
|
||||
content: "";
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -1.5rem;
|
||||
background: transparent no-repeat center center / 100% auto;
|
||||
}
|
||||
:-moz-dir(ltr) a.icon:before,
|
||||
:-moz-dir(ltr) button.icon:before { right: 0.5rem; }
|
||||
:-moz-dir(rtl) a.icon:before,
|
||||
:-moz-dir(rtl) button.icon:before { left: 0.5rem; }
|
||||
|
||||
a.icon-next:before,
|
||||
button.icon-next:before {
|
||||
background-size: 3rem;
|
||||
}
|
||||
:-moz-dir(ltr) a.icon-next:before,
|
||||
:-moz-dir(ltr) button.icon-next:before {
|
||||
background-image: url(images/right-light.svg);
|
||||
}
|
||||
:-moz-dir(rtl) a.icon-next:before,
|
||||
:-moz-dir(rtl) button.icon-next:before {
|
||||
background-image: url(images/left-light.svg);
|
||||
}
|
||||
|
||||
a.icon-previous:before,
|
||||
button.icon-previous:before {
|
||||
background-size: 3rem;
|
||||
}
|
||||
:-moz-dir(ltr) a.icon-previous:before,
|
||||
:-moz-dir(ltr) button.icon-previous:before {
|
||||
left: 0.5rem;
|
||||
background-image: url(images/left-light.svg);
|
||||
}
|
||||
:-moz-dir(rtl) a.icon-previous:before,
|
||||
:-moz-dir(rtl) button.icon-previous:before {
|
||||
right: 0.5rem;
|
||||
background-image: url(images/right-light.svg);
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
SKIN: dark
|
||||
----------------------------------------------------------------- */
|
||||
|
||||
gaia-buttons[skin="dark"] {
|
||||
background: #4d4d4d;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Pressed */
|
||||
[skin="dark"] a:active,
|
||||
[skin="dark"] button:active {
|
||||
background-color: #00aacc;
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
[skin="dark"] a[aria-disabled="true"],
|
||||
[skin="dark"] button[disabled] {
|
||||
background-color: #565656;
|
||||
color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
[skin="dark"] a[aria-disabled="true"].recommend,
|
||||
[skin="dark"] button[disabled].recommend {
|
||||
background-color: #006579;
|
||||
}
|
||||
|
||||
[skin="dark"] a[aria-disabled="true"].danger,
|
||||
[skin="dark"] button[disabled].danger {
|
||||
background-color: #730f0f;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
Transitions
|
||||
----------------------------------------------------------------- */
|
||||
|
||||
[transition] {
|
||||
display: flex;
|
||||
transition: visibility var(--transition-duration);
|
||||
}
|
||||
|
||||
[transition][hidden] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[transition="slide"] {
|
||||
transform: translateY(0);
|
||||
transition: transform var(--transition-duration) ease,
|
||||
visibility var(--transition-duration);
|
||||
}
|
||||
|
||||
[transition="slide"][hidden] {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
[transition="fade"] {
|
||||
opacity: 1;
|
||||
transition: opacity var(--transition-duration) ease,
|
||||
visibility var(--transition-duration);
|
||||
}
|
||||
|
||||
[transition="fade"][hidden] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------
|
||||
RESPONSIVE: tablet
|
||||
----------------------------------------------------------------- */
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
gaia-buttons {
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
gaia-buttons:not(.inline) {
|
||||
border-top-width: 0.2rem;
|
||||
}
|
||||
/* this 'end' padding must match the buttons 'start' margin */
|
||||
gaia-buttons:not(.inline):-moz-dir(ltr) { padding-right: 3rem; }
|
||||
gaia-buttons:not(.inline):-moz-dir(rtl) { padding-left: 3rem; }
|
||||
|
||||
a,
|
||||
button {
|
||||
font-size: 2rem;
|
||||
height: 5rem;
|
||||
line-height: 5rem;
|
||||
border-radius: 2.5rem;
|
||||
max-width: 40rem;
|
||||
}
|
||||
:-moz-dir(ltr) a,
|
||||
:-moz-dir(ltr) button {
|
||||
margin-left: 3rem;
|
||||
}
|
||||
:-moz-dir(rtl) a,
|
||||
:-moz-dir(rtl) button {
|
||||
margin-right: 3rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
/* global ComponentUtils */
|
||||
|
||||
/**
|
||||
* The gaia-confirm component displays a dialog in which the user has a
|
||||
* choice to confirm or cancel the action. It may be displayed along with a
|
||||
* title, description, and image. Buttons may also be configured.
|
||||
* @requires GaiaButtons
|
||||
*/
|
||||
window.GaiaConfirm = (function(win) {
|
||||
// Extend from the HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
// Allow baseurl to be overridden (used for demo page)
|
||||
var baseurl = window.GaiaConfirmBaseurl ||
|
||||
'/shared/elements/gaia_confirm/';
|
||||
|
||||
/**
|
||||
preventDefault and prevent other listeners from hearing abut changes to a
|
||||
particular event object.
|
||||
|
||||
@param {Array[Element]|Null} unless a list of elements to allow events to hit.
|
||||
@param {Event} event triggered by addEventListener, etc...
|
||||
*/
|
||||
function eatEvent(unless, event) {
|
||||
// prevent handlers from everything else unless the target is on the list.
|
||||
if (unless && unless.indexOf(event.target) !== -1) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
proto.createdCallback = function() {
|
||||
var shadow = this.createShadowRoot();
|
||||
this._template = template.content.cloneNode(true);
|
||||
|
||||
shadow.appendChild(this._template);
|
||||
ComponentUtils.style.call(this, baseurl);
|
||||
|
||||
var confirm = this.querySelector('gaia-buttons .confirm');
|
||||
var cancel = this.querySelector('gaia-buttons .cancel');
|
||||
|
||||
if (confirm) {
|
||||
confirm.addEventListener('click', (e) => {
|
||||
eatEvent(null, e);
|
||||
this.dispatchEvent(new CustomEvent('confirm'));
|
||||
});
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
cancel.addEventListener('click', (e) => {
|
||||
eatEvent(null, e);
|
||||
this.dispatchEvent(new CustomEvent('cancel'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var template = document.createElement('template');
|
||||
template.innerHTML = `<form role="dialog" class="confirm">
|
||||
<section>
|
||||
<content select="h1"></content>
|
||||
<content select="p"></content>
|
||||
</section>
|
||||
<content select="gaia-buttons">
|
||||
</content>
|
||||
</form>`;
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('gaia-confirm', { prototype: proto });
|
||||
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/* ----------------------------------
|
||||
* Confirm
|
||||
* ---------------------------------- */
|
||||
|
||||
/* Main dialog setup */
|
||||
gaia-confirm {
|
||||
background: #2d2d2d;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: 7rem;
|
||||
font-size: 0;
|
||||
text-align: start;
|
||||
/* Using font-size: 0; we avoid the unwanted visual space (about 3px)
|
||||
created by white-spaces and break lines in the code betewen inline-block elements */
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
transition: transform ease-in-out 0.3s, visibility 0.3s;
|
||||
/**
|
||||
magic number ideally to place confirm dialogs on top.
|
||||
*/
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
gaia-confirm[hidden] {
|
||||
transform: translateY(100%);
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.confirm section {
|
||||
height: 100%;
|
||||
font-weight: 300;
|
||||
font-size: 2.2rem;
|
||||
color: #FAFAFA;
|
||||
padding: 0 1.5rem;
|
||||
box-sizing: padding-box;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
max-height: 100%;
|
||||
vertical-align: middle;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.5em;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
padding: 1rem 1.5rem 0;
|
||||
overflow: hidden;
|
||||
max-height: 7rem;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Specific component code */
|
||||
p {
|
||||
word-wrap: break-word;
|
||||
margin: 1rem 0 0;
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 0.1rem solid #686868;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
p img {
|
||||
margin-inline-end: 2rem;
|
||||
}
|
||||
p img:-moz-dir(ltr) { float: left; }
|
||||
p img:-moz-dir(rtl) { float: right; }
|
||||
|
||||
p strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
p small {
|
||||
font-size: 1.4rem;
|
||||
font-weight: normal;
|
||||
color: #cbcbcb;
|
||||
display: block;
|
||||
}
|
||||
|
||||
dl {
|
||||
border-top: 0.1rem solid #686868;
|
||||
margin: 1rem 0 0;
|
||||
overflow: hidden;
|
||||
padding-top: 1rem;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
dl > dt {
|
||||
clear: both;
|
||||
width: 7rem;
|
||||
padding-inline-start: 1.5rem;
|
||||
font-weight: 500;
|
||||
text-align: start;
|
||||
}
|
||||
dl > dt:-moz-dir(ltr) { float: left; }
|
||||
dl > dt:-moz-dir(rtl) { float: right; }
|
||||
|
||||
dl > dd {
|
||||
padding-inline-end: 1.5rem;
|
||||
font-weight: 300;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
figure img {
|
||||
padding-inline-end: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
figure figcaption {
|
||||
font-size: 2.1rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 2rem 1.5rem;
|
||||
border-top: 0.1rem solid #686868;
|
||||
list-style: none;
|
||||
font-size: 1.6rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul li {
|
||||
word-wrap: break-word;
|
||||
line-height: 2rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
ul li:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
ul li ul {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,844 @@
|
|||
'use strict';
|
||||
/* global devicePixelRatio */
|
||||
/* global GaiaGrid */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const ACTIVE_SCALE = 1.4;
|
||||
|
||||
/* This is an alternative delay used to initiate icon movement when in edit
|
||||
* mode. The normal contextmenu delay is too long once we're in edit mode
|
||||
* and causes user confusion, but a short delay globally would cause too many
|
||||
* accidental triggers of edit mode.
|
||||
*/
|
||||
const EDIT_LONG_PRESS_DELAY = 200;
|
||||
|
||||
/* The movement threshold to use for the above synthetic long-press. */
|
||||
const EDIT_LONG_PRESS_THRESHOLD = 4 * devicePixelRatio;
|
||||
|
||||
/* This delay is the time passed once users stop the finger over an icon and
|
||||
* the rearrange is performed */
|
||||
const REARRANGE_DELAY = 30;
|
||||
|
||||
/* The page is scrolled via javascript if an icon is being moved, and is
|
||||
* within a length of a page edge configured by this value */
|
||||
const EDGE_PAGE_THRESHOLD = 50;
|
||||
|
||||
/* This is the delay before calling finish when handling touchend. */
|
||||
const TOUCH_END_FINISH_DELAY = 20;
|
||||
|
||||
/* The maximum frequency with which the grid will be rearranged in response
|
||||
* to the item position changing, in ms.
|
||||
*/
|
||||
const REARRANGE_FREQUENCY = 100;
|
||||
|
||||
/* The amount of cool-down time after a rearrange animation. To avoid
|
||||
* rearranging too frequently and causing jank and user confusion.
|
||||
*/
|
||||
const REARRANGE_COOLDOWN = 750;
|
||||
|
||||
const SCREEN_HEIGHT = window.innerHeight;
|
||||
|
||||
const scrollStep = Math.round(SCREEN_HEIGHT / EDGE_PAGE_THRESHOLD);
|
||||
|
||||
/* The scroll step will be 5 times bigger over the edge */
|
||||
const maxScrollStepFactor = 5;
|
||||
|
||||
function DragDrop(gridView) {
|
||||
this.gridView = gridView;
|
||||
this.container = gridView.element;
|
||||
this.scrollable = document.documentElement;
|
||||
this.container.addEventListener('touchstart', this);
|
||||
this.container.addEventListener('touchmove', this);
|
||||
this.container.addEventListener('touchend', this);
|
||||
window.addEventListener('touchcancel', this);
|
||||
this.container.addEventListener('click', this);
|
||||
this.container.addEventListener('contextmenu', this);
|
||||
}
|
||||
|
||||
DragDrop.prototype = {
|
||||
|
||||
/**
|
||||
* The current touchmove target.
|
||||
* @type {DomElement}
|
||||
*/
|
||||
target: null,
|
||||
|
||||
/**
|
||||
* The edit-mode long-press timeout
|
||||
*/
|
||||
longPressTimeout: null,
|
||||
|
||||
/**
|
||||
* The position of the first touchstart of the current event block
|
||||
*/
|
||||
touchStart: { x: 0, y: 0, screenX: 0, screenY: 0 },
|
||||
|
||||
/**
|
||||
* If we have moved an icon, this indicates that we need to save the state
|
||||
* @type {boolean}
|
||||
*/
|
||||
dirty: false,
|
||||
|
||||
/**
|
||||
* Whether or not we are in edit mode.
|
||||
* @type {boolean}
|
||||
*/
|
||||
inEditMode: false,
|
||||
|
||||
/**
|
||||
* The item being hovered over.
|
||||
* @type {GaiaGrid.GridItem}
|
||||
*/
|
||||
hoverItem: null,
|
||||
|
||||
/**
|
||||
* The group being hovered over.
|
||||
* @type {GaiaGrid.Group}
|
||||
*/
|
||||
hoverGroup: null,
|
||||
|
||||
/*
|
||||
* An element that's aligned to the top of the grid and should be
|
||||
* considered as obscuring the grid when in edit mode.
|
||||
*/
|
||||
editHeaderElement: null,
|
||||
|
||||
/**
|
||||
* The timeout used to rearrange the grid during dragging.
|
||||
*/
|
||||
rearrangeGridTimeout: null,
|
||||
|
||||
/**
|
||||
* The time of the last reposition call.
|
||||
*/
|
||||
lastRepositionTime: 0,
|
||||
|
||||
/**
|
||||
* Returns the maximum active scale value.
|
||||
*/
|
||||
get activeScale() {
|
||||
return ACTIVE_SCALE;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if we are currently dragging an icon.
|
||||
*/
|
||||
get inDragAction() {
|
||||
return this.target && this.target.classList.contains('active');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the delay before calling finish in response to a touch-end event.
|
||||
*/
|
||||
get touchEndFinishDelay() {
|
||||
return TOUCH_END_FINISH_DELAY;
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins the drag/drop interaction.
|
||||
* Enlarges the icon.
|
||||
* Sets additional data to make the touchmove handler faster.
|
||||
*/
|
||||
begin: function(e) {
|
||||
// Stop icon launching while we are in active state
|
||||
this.gridView.stop();
|
||||
window.dispatchEvent(new CustomEvent('gaiagrid-dragdrop-begin'));
|
||||
|
||||
this.hoverItem = null;
|
||||
this.rearrangeDelay = null;
|
||||
this.doRearrange = null;
|
||||
this.enterEditMode();
|
||||
this.container.classList.add('dragging');
|
||||
this.icon.scale = ACTIVE_SCALE;
|
||||
this.icon.setActive(true);
|
||||
this.container.parentNode.style.overflow = 'hidden';
|
||||
|
||||
// Work around e.pageX/e.pageY being null (to make it easier to work with
|
||||
// injected events, or old versions of Marionette)
|
||||
this.initialPageX = (typeof e.pageX === 'undefined') ?
|
||||
this.icon.x : e.pageX;
|
||||
this.initialPageY = (typeof e.pageY === 'undefined') ?
|
||||
this.icon.y : e.pageY;
|
||||
this.currentTouch = {
|
||||
pageX: this.initialPageX,
|
||||
pageY: this.initialPageY
|
||||
};
|
||||
|
||||
var items = this.gridView.items;
|
||||
var lastElement = items[items.length - 1];
|
||||
this.maxScroll = lastElement.y + lastElement.pixelHeight +
|
||||
(this.icon.pixelHeight * ACTIVE_SCALE);
|
||||
|
||||
// If this is a group, or the sole icon in a group, make sure the
|
||||
// surrounding groups are marked as invalid-drop so you can't initiate a
|
||||
// move that would have no result.
|
||||
var itemIndex = this.icon.detail.index;
|
||||
if (this.icon.detail.type === 'divider') {
|
||||
this.icon.element.classList.add('invalid-drop');
|
||||
if (itemIndex > 0) {
|
||||
for (var i = itemIndex - 1; i >= 0; i--) {
|
||||
var item = items[i];
|
||||
if (item.detail.type === 'divider') {
|
||||
item.element.classList.add('invalid-drop');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var itemBefore = itemIndex ? items[itemIndex - 1] : null;
|
||||
var itemAfter = items[itemIndex + 1];
|
||||
|
||||
if ((itemAfter.detail.type === 'placeholder' ||
|
||||
itemAfter.detail.type === 'divider') &&
|
||||
(!itemBefore || itemBefore.detail.type === 'divider')) {
|
||||
if (itemBefore) {
|
||||
itemBefore.element.classList.add('invalid-drop');
|
||||
}
|
||||
var group, groupIndex = itemIndex;
|
||||
do {
|
||||
group = items[++groupIndex];
|
||||
} while (group.detail.type !== 'divider');
|
||||
group.element.classList.add('invalid-drop');
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw the icon at the new position and scale
|
||||
this.updateIconPosition();
|
||||
|
||||
// Rearrange grid to highlight the group underneath the icon
|
||||
this.rearrangeGrid();
|
||||
},
|
||||
|
||||
finish: function() {
|
||||
// Complete the repositioning timeout
|
||||
if (this.rearrangeGridTimeout !== null) {
|
||||
this.rearrangeGrid();
|
||||
}
|
||||
|
||||
// Remove the dragging property after the icon has transitioned into
|
||||
// place to avoid jank due to animations starting that are disabled
|
||||
// when dragging.
|
||||
this.icon.element.addEventListener('transitionend', this);
|
||||
this.currentTouch = null;
|
||||
|
||||
if (this.doRearrange !== null) {
|
||||
if (this.rearrangeDelay) {
|
||||
clearTimeout(this.rearrangeDelay);
|
||||
}
|
||||
this.doRearrange.call(this);
|
||||
} else {
|
||||
this.icon.requestAttention();
|
||||
}
|
||||
|
||||
// Hand back responsibility to Grid view to render the dragged item.
|
||||
this.icon.scale = 1;
|
||||
this.icon.setActive(false);
|
||||
this.icon.element.classList.remove('hovering');
|
||||
this.target = null;
|
||||
|
||||
this.gridView.render();
|
||||
|
||||
// Save icon state if we need to
|
||||
if (this.dirty) {
|
||||
window.dispatchEvent(new CustomEvent('gaiagrid-saveitems'));
|
||||
}
|
||||
this.dirty = false;
|
||||
|
||||
setTimeout(function nextTick() {
|
||||
this.gridView.start();
|
||||
window.dispatchEvent(new CustomEvent('gaiagrid-dragdrop-finish'));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
finalize: function() {
|
||||
this.container.parentNode.style.overflow = '';
|
||||
this.container.classList.remove('dragging');
|
||||
this.container.classList.remove('hover-over-top');
|
||||
if (this.icon) {
|
||||
this.icon.element.removeEventListener('transitionend', this);
|
||||
this.icon = null;
|
||||
}
|
||||
if (this.hoverItem) {
|
||||
this.hoverItem.element.classList.remove('hovered');
|
||||
this.hoverItem = null;
|
||||
}
|
||||
if (this.hoverGroup) {
|
||||
this.hoverGroup.element.classList.remove('drop-target');
|
||||
this.hoverGroup = null;
|
||||
}
|
||||
for (var i = 0, iLen = this.gridView.items.length;
|
||||
i < iLen; i++) {
|
||||
var item = this.gridView.items[i];
|
||||
item.element.classList.remove('invalid-drop');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The closer to edge the faster (bigger step).
|
||||
** Distance 0px -> 5 times faster
|
||||
** Distance 25px -> 2.5 times faster
|
||||
** Distance 50px (EDGE_PAGE_THRESHOLD) -> 0 times
|
||||
*/
|
||||
getScrollStep: function(distanceToEdge) {
|
||||
var factor = maxScrollStepFactor;
|
||||
|
||||
if (distanceToEdge > 0) {
|
||||
factor *= ((EDGE_PAGE_THRESHOLD - distanceToEdge) /
|
||||
EDGE_PAGE_THRESHOLD);
|
||||
}
|
||||
|
||||
return Math.round(scrollStep * factor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Positions the dragged icon, rearranges the grid and scrolls the page if
|
||||
* needed.
|
||||
* The page is scrolled via javascript if an icon is being moved,
|
||||
* and is within a percentage of a page edge.
|
||||
*/
|
||||
positionAndScrollIfNeeded: function() {
|
||||
var touch = this.currentTouch;
|
||||
if (!touch || !this.inDragAction) {
|
||||
this.isScrolling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollStep;
|
||||
var docScroll = this.scrollable.scrollTop;
|
||||
var distanceFromTop = Math.abs(touch.pageY - docScroll);
|
||||
var distanceFromHeader = distanceFromTop -
|
||||
(this.editHeaderElement ? this.editHeaderElement.clientHeight : 0);
|
||||
this.isScrolling = true;
|
||||
|
||||
if (distanceFromTop > SCREEN_HEIGHT - EDGE_PAGE_THRESHOLD) {
|
||||
var maxY = this.maxScroll;
|
||||
scrollStep = this.getScrollStep(SCREEN_HEIGHT - distanceFromTop);
|
||||
// We cannot exceed the maximum scroll value
|
||||
if (touch.pageY >= maxY || maxY - touch.pageY < scrollStep) {
|
||||
this.isScrolling = false;
|
||||
}
|
||||
} else if (touch.pageY > 0 && distanceFromHeader < EDGE_PAGE_THRESHOLD) {
|
||||
// We cannot go below the minimum scroll value
|
||||
scrollStep = -this.getScrollStep(distanceFromHeader);
|
||||
scrollStep = Math.max(scrollStep, -this.scrollable.scrollTop);
|
||||
if (scrollStep >= 0) {
|
||||
this.isScrolling = false;
|
||||
}
|
||||
} else {
|
||||
this.isScrolling = false;
|
||||
}
|
||||
|
||||
if (!this.isScrolling) {
|
||||
this.updateIconPosition();
|
||||
this.deferredRearrangeGrid();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateIconPosition();
|
||||
this.scrollable.scrollTop += scrollStep;
|
||||
touch.pageY += scrollStep;
|
||||
|
||||
exports.requestAnimationFrame(this.positionAndScrollIfNeeded.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlights the group of the given icon index, or the group if the index
|
||||
* belongs to a group.
|
||||
* @param {Integer} index The grid index of the icon or group.
|
||||
*/
|
||||
highlightGroup: function(index) {
|
||||
for (var i = index, iLen = this.gridView.items.length;
|
||||
i < iLen; i++) {
|
||||
var item = this.gridView.items[i];
|
||||
if (item.detail.type === 'divider') {
|
||||
if (this.hoverGroup != item) {
|
||||
if (this.hoverGroup) {
|
||||
this.hoverGroup.element.classList.remove('drop-target');
|
||||
}
|
||||
this.hoverGroup = item;
|
||||
item.element.classList.add('drop-target');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
inFirstGroup: function(index) {
|
||||
for (var i = index; i >= 0; i--) {
|
||||
if (this.gridView.items[i].detail.type === 'divider') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Positions an icon on the grid using the current touch coordinates.
|
||||
*/
|
||||
updateIconPosition: function() {
|
||||
var oldX = this.icon.x;
|
||||
var oldY = this.icon.y;
|
||||
var newX = this.currentTouch.pageX - (this.initialPageX - this.icon.x);
|
||||
var newY = this.currentTouch.pageY - (this.initialPageY - this.icon.y);
|
||||
|
||||
// Adjust new icon coordinates for the slightly inflated scale so that
|
||||
// it appears centered around the touch point. Dividers aren't scaled
|
||||
// during dragging.
|
||||
if (this.icon.detail.type !== 'divider') {
|
||||
newX = newX - ((this.icon.scale * this.gridView.layout.gridItemWidth) -
|
||||
this.gridView.layout.gridItemWidth) / 2;
|
||||
newY = newY - ((this.icon.scale * this.icon.pixelHeight) -
|
||||
this.icon.pixelHeight) / 2;
|
||||
}
|
||||
|
||||
this.icon.setCoordinates(newX, newY);
|
||||
this.icon.render();
|
||||
this.icon.setCoordinates(oldX, oldY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Will call rearrangeGrid after a short period, calculated based on how
|
||||
* recently the grid was last rearranged.
|
||||
*/
|
||||
deferredRearrangeGrid: function() {
|
||||
if (this.rearrangeGridTimeout !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var delay = Math.max(
|
||||
REARRANGE_FREQUENCY, REARRANGE_COOLDOWN -
|
||||
Math.max(0, Date.now() - this.lastRepositionTime));
|
||||
this.rearrangeGridTimeout = setTimeout(() => {
|
||||
this.rearrangeGridTimeout = null;
|
||||
this.rearrangeGrid();
|
||||
}, delay);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges the grid icons and groups based on the current position of
|
||||
* the dragged icon.
|
||||
*/
|
||||
rearrangeGrid: function() {
|
||||
if (this.rearrangeGridTimeout !== null) {
|
||||
clearTimeout(this.rearrangeGridTimeout);
|
||||
this.rearrangeGridTimeout = null;
|
||||
}
|
||||
|
||||
if (this.isScrolling) {
|
||||
// We don't want the grid to shift while we're auto-scrolling
|
||||
return;
|
||||
}
|
||||
|
||||
var iconIsDivider = this.icon.detail.type === 'divider';
|
||||
var pageX = this.currentTouch.pageX - (this.initialPageX - this.icon.x);
|
||||
var pageY = this.currentTouch.pageY - (this.initialPageY - this.icon.y);
|
||||
|
||||
// Reposition in the icons array if necessary.
|
||||
// Find the icon with the closest X/Y position of the move,
|
||||
// and insert ours before it.
|
||||
var foundIndex = 0;
|
||||
var insertDividerAtTop = !this.gridView.config.features.disableSections;
|
||||
pageX += this.gridView.layout.gridItemWidth / 2;
|
||||
pageY += this.icon.pixelHeight / 2;
|
||||
if (pageY >= 0) {
|
||||
foundIndex =
|
||||
this.gridView.getNearestItemIndex(pageX, pageY, iconIsDivider);
|
||||
|
||||
// If we're dragging a group over the first icon, a divider will
|
||||
// be inserted at the top.
|
||||
if (!(iconIsDivider && foundIndex === 0)) {
|
||||
this.container.classList.remove('hover-over-top');
|
||||
insertDividerAtTop = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the rearrange callback and hover item if we aren't hovering over
|
||||
// anything.
|
||||
this.doRearrange = null;
|
||||
if (this.rearrangeDelay) {
|
||||
clearTimeout(this.rearrangeDelay);
|
||||
this.rearrangeDelay = null;
|
||||
}
|
||||
if (this.hoverItem) {
|
||||
this.hoverItem.element.classList.remove('hovered');
|
||||
this.hoverItem = null;
|
||||
}
|
||||
|
||||
// Add the 'hovering' class to the dragged icon.
|
||||
if (foundIndex !== null) {
|
||||
this.icon.element.classList.add('hovering');
|
||||
} else {
|
||||
this.icon.element.classList.remove('hovering');
|
||||
}
|
||||
|
||||
// Nothing to do if we find the dragged icon or no icon
|
||||
if (!insertDividerAtTop &&
|
||||
(foundIndex === null || foundIndex === this.icon.detail.index)) {
|
||||
if (!iconIsDivider) {
|
||||
this.highlightGroup(this.icon.detail.index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var foundItem = this.gridView.items[foundIndex];
|
||||
|
||||
// If we're a divider being dragged, only allow dropping on either the
|
||||
// first item or another divider.
|
||||
if (iconIsDivider &&
|
||||
foundIndex > 0 &&
|
||||
foundItem.detail.type !== 'divider') {
|
||||
// Remove the hovering class from a group if it's not hovering over
|
||||
// a valid drop position or itself.
|
||||
this.icon.element.classList.remove('hovering');
|
||||
return;
|
||||
}
|
||||
|
||||
// If the item isn't a group, trigger the
|
||||
// hovered state on the found item.
|
||||
if (!insertDividerAtTop && !iconIsDivider) {
|
||||
this.hoverItem = foundItem;
|
||||
this.hoverItem.element.classList.add('hovered');
|
||||
}
|
||||
|
||||
// Delay rearranging when we're dragging groups to when they're dropped
|
||||
var rearrangeAfterDelay = !iconIsDivider;
|
||||
|
||||
// Determine if we want to create a new divider. We do this when hovering
|
||||
// over a divider with an icon, or when dragging an icon to the very top
|
||||
// of the grid.
|
||||
var createDivider = insertDividerAtTop;
|
||||
if (!insertDividerAtTop && !iconIsDivider &&
|
||||
(foundItem.detail.type === 'divider')) {
|
||||
// Allow dropping into a collapsed group if the new position is in
|
||||
// the top 2/3 of the group.
|
||||
if (foundItem.detail.collapsed &&
|
||||
pageY <= foundItem.y + (foundItem.pixelHeight * 2/3)) {
|
||||
rearrangeAfterDelay = false;
|
||||
foundItem.element.classList.remove('hovered');
|
||||
|
||||
// When dragging an item forwards, it gets placed after the found
|
||||
// item, adjust to the item before the divider.
|
||||
if (this.icon.detail.index < foundIndex) {
|
||||
foundItem = this.gridView.items[foundIndex - 1];
|
||||
}
|
||||
} else {
|
||||
createDivider = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (createDivider) {
|
||||
// Remove the group background highlight if we're between groups
|
||||
if (this.hoverGroup) {
|
||||
this.hoverGroup.element.classList.remove('drop-target');
|
||||
this.hoverGroup = null;
|
||||
}
|
||||
|
||||
// Cancel rearrangement if it would have no effect.
|
||||
var redundantRearrange = false;
|
||||
if (insertDividerAtTop) {
|
||||
if (iconIsDivider) {
|
||||
redundantRearrange = this.inFirstGroup(this.icon.detail.index - 1);
|
||||
} else {
|
||||
redundantRearrange =
|
||||
(this.icon.detail.index === 0 &&
|
||||
this.gridView.items[1].detail.type === 'placeholder');
|
||||
}
|
||||
} else {
|
||||
redundantRearrange = this.gridView.items[foundIndex].element.
|
||||
classList.contains('invalid-drop');
|
||||
}
|
||||
|
||||
if (redundantRearrange) {
|
||||
if (this.hoverItem) {
|
||||
this.hoverItem.element.classList.remove('hovered');
|
||||
this.hoverItem = null;
|
||||
}
|
||||
return;
|
||||
} else if (insertDividerAtTop) {
|
||||
this.container.classList.add('hover-over-top');
|
||||
}
|
||||
|
||||
this.doRearrange =
|
||||
this.createNewDivider.bind(this,
|
||||
insertDividerAtTop ? null : foundItem);
|
||||
} else {
|
||||
if (!iconIsDivider) {
|
||||
// Change the display of the group the icon is hovering over to
|
||||
// indicate it can be dropped.
|
||||
this.highlightGroup(foundIndex);
|
||||
}
|
||||
|
||||
this.doRearrange = this.rearrange.bind(this, foundItem);
|
||||
|
||||
if (rearrangeAfterDelay) {
|
||||
this.rearrangeDelay =
|
||||
setTimeout(this.doRearrange.bind(this), REARRANGE_DELAY);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new divider in GridView.items and rearranges the currently
|
||||
* dragged icon into the newly created section.
|
||||
* @param {Object} tDivider The divider after which to place a new divider,
|
||||
* or null to insert a divider at the beginning on the grid.
|
||||
*/
|
||||
createNewDivider: function(tDivider) {
|
||||
var items = this.gridView.items;
|
||||
var tIndex = tDivider ? items.indexOf(tDivider) + 1 : 0;
|
||||
|
||||
// Create the new divider
|
||||
var newDivider = new GaiaGrid.Divider();
|
||||
items.splice(tIndex, 0, newDivider);
|
||||
|
||||
// Place the dragged item into the new empty section
|
||||
var sIndex = items.indexOf(this.icon);
|
||||
this.rearrange(sIndex >= tIndex ? newDivider : tDivider);
|
||||
|
||||
// Make sure the new divider/group is in view
|
||||
newDivider.requestAttention();
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges items in GridView.items
|
||||
* @param {Object} tItem The item to position the dragged icon at.
|
||||
*/
|
||||
rearrange: function(tItem) {
|
||||
// Clear the rearrange callback
|
||||
this.doRearrange = null;
|
||||
this.rearrangeDelay = null;
|
||||
|
||||
// We get a reference to the position of this.icon within the items
|
||||
// array. Because placeholders are shifting around while we are dragging,
|
||||
// we can't trust the detail.index attribute. This will be fixed on every
|
||||
// render call though.
|
||||
var sIndex = this.gridView.items.indexOf(this.icon);
|
||||
var tIndex = this.gridView.items.indexOf(tItem);
|
||||
|
||||
// Check how many items we need to rearrange (if it's a group, there will
|
||||
// be multiple items.
|
||||
var eIndex = sIndex;
|
||||
if (this.icon.detail.type === 'divider') {
|
||||
for (; sIndex > 0 &&
|
||||
this.gridView.items[sIndex - 1].detail.type !== 'divider';
|
||||
sIndex--) {}
|
||||
|
||||
// Modify the tIndex to make sure we're inserting immediately after
|
||||
// another divider or at the start of the container.
|
||||
if (this.gridView.items[tIndex].detail.type === 'divider') {
|
||||
tIndex++;
|
||||
} else {
|
||||
tIndex = 0;
|
||||
}
|
||||
|
||||
if (sIndex === tIndex) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
} else if (sIndex < tIndex) {
|
||||
tIndex++;
|
||||
}
|
||||
|
||||
// We may have changed tIndex, so refresh our item pointer
|
||||
tItem = this.gridView.items[tIndex];
|
||||
|
||||
// Remove the items from the array
|
||||
var lastItemOffset = eIndex - sIndex;
|
||||
var toInsert = this.gridView.items.splice(sIndex, lastItemOffset + 1);
|
||||
|
||||
// Craft the parameter list to reinsert the items at the correct place
|
||||
toInsert.unshift(tItem ? this.gridView.items.indexOf(tItem) :
|
||||
this.gridView.items.length, 0);
|
||||
|
||||
// Reinsert items
|
||||
this.rearrangeDelay = null;
|
||||
this.dirty = true;
|
||||
this.gridView.items.splice.apply(this.gridView.items, toInsert);
|
||||
|
||||
var oldX = this.icon.x;
|
||||
var oldY = this.icon.y;
|
||||
this.gridView.render();
|
||||
|
||||
// In this case, we don't want to compensate for the icon moving, so
|
||||
// we need to correct our initial values to stop the icon from jumping
|
||||
// after rearranging.
|
||||
this.initialPageX -= oldX - this.icon.x;
|
||||
this.initialPageY -= oldY - this.icon.y;
|
||||
|
||||
this.lastRepositionTime = Date.now();
|
||||
},
|
||||
|
||||
enterEditMode: function() {
|
||||
this.inEditMode = true;
|
||||
this.container.classList.add('edit-mode');
|
||||
document.body.classList.add('edit-mode');
|
||||
this.gridView.element.dispatchEvent(
|
||||
new CustomEvent('editmode-start'));
|
||||
document.addEventListener('visibilitychange', this);
|
||||
this.gridView.render();
|
||||
},
|
||||
|
||||
exitEditMode: function() {
|
||||
// If we're in the middle of a drag, cancel it.
|
||||
if (this.icon) {
|
||||
this.finish();
|
||||
this.finalize();
|
||||
}
|
||||
|
||||
this.inEditMode = false;
|
||||
this.cancelLongPressTimeout();
|
||||
this.container.classList.remove('edit-mode');
|
||||
document.body.classList.remove('edit-mode');
|
||||
this.gridView.element.dispatchEvent(new CustomEvent('editmode-end'));
|
||||
document.removeEventListener('visibilitychange', this);
|
||||
this.gridView.render();
|
||||
},
|
||||
|
||||
cancelLongPressTimeout: function() {
|
||||
if (this.longPressTimeout !== null) {
|
||||
clearTimeout(this.longPressTimeout);
|
||||
this.longPressTimeout = null;
|
||||
}
|
||||
},
|
||||
|
||||
inLongPressThreshold: function(x, y) {
|
||||
return (
|
||||
Math.abs(x - this.touchStart.screenX) < EDIT_LONG_PRESS_THRESHOLD &&
|
||||
Math.abs(y - this.touchStart.screenY) < EDIT_LONG_PRESS_THRESHOLD);
|
||||
},
|
||||
|
||||
/**
|
||||
* General event handler.
|
||||
*/
|
||||
handleEvent: function(e) {
|
||||
switch(e.type) {
|
||||
case 'visibilitychange':
|
||||
if (document.hidden) {
|
||||
this.exitEditMode();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'touchstart':
|
||||
this.cancelLongPressTimeout();
|
||||
|
||||
if (e.touches.length > 1) {
|
||||
if (this.inDragAction) {
|
||||
this.finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.inEditMode && !this.icon) {
|
||||
this.touchStart.x = e.touches[0].pageX;
|
||||
this.touchStart.y = e.touches[0].pageY;
|
||||
this.touchStart.screenX = e.touches[0].screenX;
|
||||
this.touchStart.screenY = e.touches[0].screenY;
|
||||
this.longPressTimeout = setTimeout(() => {
|
||||
this.longPressTimeout = null;
|
||||
this.handleEvent({
|
||||
type: 'contextmenu',
|
||||
target: e.target,
|
||||
pageX: this.touchStart.x,
|
||||
pageY: this.touchStart.y,
|
||||
preventDefault: function() {},
|
||||
stopImmediatePropagation: function() {}
|
||||
});
|
||||
}, EDIT_LONG_PRESS_DELAY);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contextmenu':
|
||||
this.cancelLongPressTimeout();
|
||||
|
||||
if (this.icon) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.defaultPrevented) {
|
||||
// other handlers already handled this.
|
||||
// in the future, we should use the shadow root and dispatch a
|
||||
// "contextmenu" event from here instead.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.icon = this.gridView.findItemFromElement(e.target, true);
|
||||
|
||||
if (!this.icon || !this.icon.element || !this.icon.isDraggable() ||
|
||||
this.icon.detail.type === 'placeholder') {
|
||||
this.icon = null;
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.target = this.icon.element;
|
||||
this.begin(e);
|
||||
|
||||
break;
|
||||
|
||||
case 'touchmove':
|
||||
var touch = e.touches[0];
|
||||
|
||||
if (this.inDragAction) {
|
||||
this.currentTouch = {
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY
|
||||
};
|
||||
|
||||
if (!this.isScrolling) {
|
||||
this.positionAndScrollIfNeeded();
|
||||
}
|
||||
} else if (this.longPressTimeout !== null &&
|
||||
!this.inLongPressThreshold(touch.screenX,
|
||||
touch.screenY)) {
|
||||
this.cancelLongPressTimeout();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'click':
|
||||
case 'touchcancel':
|
||||
if (this.inDragAction) {
|
||||
this.finish();
|
||||
this.finalize();
|
||||
}
|
||||
|
||||
this.cancelLongPressTimeout();
|
||||
break;
|
||||
|
||||
case 'touchend':
|
||||
if (this.inDragAction) {
|
||||
// Ensure the app is not launched
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
// As contextmenu event can be synthesized, it's possible for it
|
||||
// to happen in the same event-loop as touchend, meaning
|
||||
// finish would be called immediately after begin. This would
|
||||
// mean that the icon doesn't get a chance to transition and
|
||||
// the transitionend that we expect won't be called, so finalize
|
||||
// also won't be called, leaving us in an inconsistent state.
|
||||
// It would be so great if there was a transitionstart event :(
|
||||
setTimeout(() => { this.finish(); }, TOUCH_END_FINISH_DELAY);
|
||||
}
|
||||
|
||||
this.cancelLongPressTimeout();
|
||||
break;
|
||||
|
||||
case 'transitionend':
|
||||
this.finalize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.GridDragDrop = DragDrop;
|
||||
|
||||
}(window));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;'use strict';(function(exports){const SHADOW_BLUR=1;const SHADOW_OFFSET_Y=1;const SHADOW_OFFSET_X=1;const SHADOW_COLOR='rgba(0, 0, 0, 0.2)';const DEFAULT_BACKGROUND_COLOR='rgb(228, 234, 238)';const UNSCALED_CANVAS_PADDING=2;const CANVAS_PADDING=UNSCALED_CANVAS_PADDING*devicePixelRatio;function IconRenderer(icon){this._icon=icon;}
|
||||
IconRenderer.TYPE={CLIP:'clip',FAVICON:'favicon',STANDARD:'standard',};IconRenderer.prototype={unscaledCanvasPadding:UNSCALED_CANVAS_PADDING*2,get _maxSize(){return this._icon.grid.layout.gridMaxIconSize;},_createCanvas:function(){const CANVAS_PADDING_BOTTOM=1*devicePixelRatio;var canvas=document.createElement('canvas');canvas.width=this._maxSize+(CANVAS_PADDING*2);canvas.height=this._maxSize+CANVAS_PADDING+CANVAS_PADDING_BOTTOM;return canvas;},_createClipCanvas:function(){var canvas=document.createElement('canvas');canvas.width=this._maxSize+(CANVAS_PADDING*2);canvas.height=this._maxSize+(CANVAS_PADDING*2);return canvas;},_decorateShadowCanvas:function(canvas){var ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.shadowColor=SHADOW_COLOR;ctx.shadowBlur=SHADOW_BLUR;ctx.shadowOffsetY=SHADOW_OFFSET_Y;ctx.shadowOffsetX=SHADOW_OFFSET_X;return ctx;},clip:function(img){return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);var clipCanvas=this._createClipCanvas();var clipCtx=clipCanvas.getContext('2d',{willReadFrequently:true});clipCtx.beginPath();clipCtx.arc(clipCanvas.width/2,clipCanvas.height/2,clipCanvas.height/2,0,2*Math.PI);clipCtx.clip();clipCtx.drawImage(img,0,0,clipCanvas.width,clipCanvas.height);shadowCtx.drawImage(clipCanvas,CANVAS_PADDING,CANVAS_PADDING,this._maxSize,this._maxSize);shadowCanvas.toBlob(resolve);});},favicon:function(img){if(img.width>this._icon.grid.layout.gridIconSize/2){return this.clip(img);}
|
||||
return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);var iconWidth;var iconHeight;shadowCtx.beginPath();shadowCtx.arc(shadowCanvas.width/2,shadowCanvas.height/2,shadowCanvas.height/2-CANVAS_PADDING,0,2*Math.PI,false);shadowCtx.fillStyle=DEFAULT_BACKGROUND_COLOR;shadowCtx.fill();iconWidth=iconHeight=this._maxSize*0.55;shadowCtx.shadowBlur=0;shadowCtx.shadowOffsetY=0;shadowCtx.mozImageSmoothingEnabled=false;shadowCtx.drawImage(img,(shadowCanvas.width-iconWidth)/2,(shadowCanvas.height-iconHeight)/2,iconWidth,iconHeight);shadowCanvas.toBlob(resolve);});},standard:function(img){return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);shadowCtx.drawImage(img,CANVAS_PADDING,CANVAS_PADDING,this._maxSize,this._maxSize);shadowCanvas.toBlob(resolve);});}};exports.GridIconRenderer=IconRenderer;}(window));
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
;'use strict';(function(exports){const maxIconsPerRow=4;const iconScaleFactorMaxIconsPerRow=5;const minIconsPerRow=3;const iconScaleFactorMinIconsPerRow=3.8;const distanceBetweenIconsWithMinIconsPerRow=36;const distanceBetweenIconsWithMaxIconsPerRow=38;const eachTextRowHeight=20;var constraintSize;function GridLayout(gridView){this.gridView=gridView;if(window.verticalPreferences){verticalPreferences.get('grid.cols').then(value=>{this.cols=value;this.onReady();},this.onReady);verticalPreferences.addEventListener('updated',this);}else{this.onReady();}}
|
||||
GridLayout.prototype={minIconsPerRow:minIconsPerRow,maxIconsPerRow:maxIconsPerRow,_cols:minIconsPerRow,_offsetY:0,_percent:1,_dividerLineHeight:0,get percent(){return this._percent;},get cols(){return this._cols;},set cols(value){if(!value||value===this._cols){return;}
|
||||
if(window.verticalPreferences){verticalPreferences.put('grid.cols',value);}
|
||||
this._offsetY=0;this._percent=value==minIconsPerRow?1:minIconsPerRow/maxIconsPerRow;this._cols=value;},get constraintSize(){if(!constraintSize){this.calculateSize();}
|
||||
return constraintSize;},get gridItemHeight(){var height=this.gridIconSize+
|
||||
(this._cols===minIconsPerRow?distanceBetweenIconsWithMinIconsPerRow:distanceBetweenIconsWithMaxIconsPerRow);var defaultTextRows=2;var definedRows=parseInt(this.gridView.element.getAttribute('text-rows'),10);if(definedRows>defaultTextRows){height+=(definedRows-defaultTextRows)*eachTextRowHeight;}
|
||||
return height;},get gridItemWidth(){return this.constraintSize/this._cols;},get gridMaxIconSize(){var baseSize=(this.constraintSize/iconScaleFactorMinIconsPerRow);return baseSize*devicePixelRatio;},get gridIconSize(){var numCols=this._cols;var size=this.constraintSize/numCols;if(numCols===minIconsPerRow){size=this.constraintSize/iconScaleFactorMinIconsPerRow;}else if(numCols===maxIconsPerRow){size=this.constraintSize/iconScaleFactorMaxIconsPerRow;}
|
||||
return Math.floor(size);},get offsetY(){return this._offsetY;},set offsetY(value){this._offsetY=value;},stepYAxis:function(value){this._offsetY+=value;},handleEvent:function(e){switch(e.type){case'updated':var prop=e.target;if(prop.name==='grid.cols'){this.cols=parseInt(prop.value,10);this.gridView.render();}
|
||||
break;}},calculateSize:function(){constraintSize=Math.min(window.screen.width,window.screen.height);},onReady:function(){window.dispatchEvent(new CustomEvent('gaiagrid-layout-ready'));}};exports.GridLayout=GridLayout;}(window));
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
;'use strict';(function(exports){const APP_LAUNCH_TIMEOUT=3000;function GridView(config){this.config=config;this.clickIcon=this.clickIcon.bind(this);this.onVisibilityChange=this.onVisibilityChange.bind(this);if(config.features.zoom){this.zoom=new GridZoom(this);}
|
||||
this.layout=new GridLayout(this);if(config.element.hasAttribute('cols')){this.layout.cols=parseInt(config.element.getAttribute('cols'),10);}
|
||||
this.start();}
|
||||
GridView.prototype={icons:{},items:[],get element(){return this.config.element;},set cols(value){this.layout.cols=value;},_launchingApp:false,add:function(item,insertTo,expandGroup){if(!item){return;}
|
||||
if(item.identifier){if(this.icons[item.identifier]){console.log('Error, duplicate identifier: ',item.identifier,new Error().stack);return;}
|
||||
this.icons[item.identifier]=item;}else if(item.detail.type!=='divider'&&item.detail.type!=='placeholder'){console.log('Error, could not load identifier for object: ',JSON.stringify(item.detail));return;}
|
||||
if(!isNaN(parseFloat(insertTo))&&isFinite(insertTo)){this.items.splice(insertTo,0,item);}else{insertTo=this.items.length;this.items.push(item);}
|
||||
if(expandGroup){for(var i=insertTo+1,iLen=this.items.length;i<iLen;i++){var divider=this.items[i];if(divider.detail.type==='divider'){if(divider.detail.collapsed){divider.expand();}
|
||||
break;}}}},getNearestItemIndex:function(x,y,isRow){var foundIndex=null;var leastDistance=null;var itemMiddleOffset=this.layout.gridItemWidth/2;for(var i=0,iLen=this.items.length;i<iLen;i++){var item=this.items[i];if(!item.isDraggable()){continue;}
|
||||
if(this.config.features.disableSections&&item.detail.type==='divider'){continue;}
|
||||
if(item.detail.type!=='divider'&&item.element.classList.contains('collapsed')){continue;}
|
||||
var middleX=item.x+itemMiddleOffset;var middleY=item.y+item.pixelHeight/2;var xDistance=(isRow||item.detail.type==='divider')?0:x-middleX;var yDistance=y-middleY;var distance=Math.sqrt(xDistance*xDistance+
|
||||
yDistance*yDistance);if(leastDistance===null||distance<leastDistance){leastDistance=distance;foundIndex=i;}}
|
||||
return foundIndex;},start:function(){this.element.addEventListener('click',this.clickIcon);window.addEventListener('visibilitychange',this.onVisibilityChange);},stop:function(){this.element.removeEventListener('click',this.clickIcon);window.removeEventListener('visibilitychange',this.onVisibilityChange);},findItemFromElement:function(element,excludeCollapsedIcons){while(element&&element.parentNode!==this.element){element=element.parentNode;}
|
||||
if(!element){return null;}
|
||||
var i,iLen=this.items.length;var identifier=element.dataset.identifier;var icon=this.icons[identifier];if(!icon){for(i=0;i<iLen;i++){if(this.items[i].element===element){icon=this.items[i];break;}}}
|
||||
if(icon&&excludeCollapsedIcons){if(icon.detail.type!=='divider'&&icon.detail.type!=='placeholder'&&icon.element.classList.contains('collapsed')){for(i=icon.detail.index+1;i<iLen;i++){if(this.items[i].detail.type==='divider'){return this.items[i];}}
|
||||
console.warn('Collapsed icon found with no group');icon=null;}}
|
||||
return icon;},onVisibilityChange:function(){this._launchingApp=false;},clickIcon:function(e){e.preventDefault();var inEditMode=this.dragdrop&&this.dragdrop.inEditMode;var action='launch';if(e.target.classList.contains('remove')){action='remove';}
|
||||
var icon=this.findItemFromElement(e.target);if(!icon){return;}
|
||||
if(action==='launch'){if(inEditMode&&e.target.classList.contains('icon')){if(!icon.isEditable()){return;}
|
||||
action='edit';}else{if(!icon[action]){return;}
|
||||
icon.element.classList.add('launching');var returnTimeout=500;setTimeout(function stateReturn(){if(icon.element){icon.element.classList.remove('launching');}},returnTimeout);}}
|
||||
if((icon.detail.type==='app'||icon.detail.type==='bookmark')&&this._launchingApp){return;}
|
||||
if((icon.detail.type==='app'&&icon.appState==='ready')||icon.detail.type==='bookmark'){this._launchingApp=true;if(this._launchingTimeout){window.clearTimeout(this._launchingTimeout);this._launchingTimeout=null;}
|
||||
this._launchingTimeout=window.setTimeout(function(){this._launchingTimeout=null;this._launchingApp=false;}.bind(this),APP_LAUNCH_TIMEOUT);}
|
||||
icon[action](e.target);},cleanItems:function(skipDivider){var appCount=0;var toRemove=[];this.items.forEach(function(item,idx){if(item instanceof GaiaGrid.Divider){if(appCount===0){toRemove.push(idx);}
|
||||
appCount=0;}else{appCount++;}},this);toRemove.reverse();toRemove.forEach(function(idx){var removed=this.items.splice(idx,1)[0];removed.remove();},this);if(skipDivider){return;}
|
||||
var lastItem=this.items[this.items.length-1];if(!lastItem||!(lastItem instanceof GaiaGrid.Divider)){this.items.push(new GaiaGrid.Divider());}},removeAllPlaceholders:function(){var toSplice=[];var previousItem;this.items.forEach(function(item,idx){if(item instanceof GaiaGrid.Placeholder){if((!previousItem||(previousItem&&previousItem instanceof GaiaGrid.Divider))&&this.dragdrop&&this.dragdrop.inDragAction){return;}
|
||||
toSplice.push(idx);}
|
||||
previousItem=item;},this);toSplice.reverse().forEach(function(idx){this.items.splice(idx,1)[0].remove();},this);},clear:function(){for(var i=0,iLen=this.items.length;i<iLen;i++){var item=this.items[i];if(item.element){this.element.removeChild(item.element);item.element=null;item.lastX=null;item.lastY=null;item.lastScale=null;}}
|
||||
this.items=[];this.icons={};},createPlaceholders:function(coordinates,idx,count){var isRTL=(document.documentElement.dir==='rtl');for(var i=0;i<count;i++){var item=new GaiaGrid.Placeholder();this.items.splice(idx+i,0,item);item.setPosition(idx+i);var xPosition=(coordinates[0]+i)*this.layout.gridItemWidth;if(isRTL){xPosition=(this.layout.constraintSize-this.layout.gridItemWidth)-
|
||||
xPosition;}
|
||||
item.setCoordinates(xPosition,this.layout.offsetY);item.render();}},render:function(options){var self=this;options=options||{};this.removeAllPlaceholders();this.cleanItems(options.skipDivider);var oldHeight=this.layout.offsetY;this.layout.offsetY=0;var x=0;var y=0;function step(item){var pixelHeight=item.pixelHeight;self.layout.stepYAxis(pixelHeight);x=0;y++;}
|
||||
var pendingCachedIcons=0;var onCachedIconRendered=()=>{if(--pendingCachedIcons<=0){this.element.removeEventListener('cached-icon-rendered',onCachedIconRendered);this.element.dispatchEvent(new CustomEvent('cached-icons-rendered'));}};this.element.addEventListener('cached-icon-rendered',onCachedIconRendered);var nextDivider=null;var oddDivider=true;var isRTL=(document.documentElement.dir==='rtl');for(var idx=0;idx<this.items.length;idx++){var item=this.items[idx];if(options.rerender&&item.element){this.element.removeChild(item.element);item.element=null;}
|
||||
if(item.detail.type==='divider'){nextDivider=null;}else{if(!nextDivider){for(var i=idx+1;i<this.items.length;i++){if(this.items[i].detail.type==='divider'){nextDivider=this.items[i];oddDivider=!oddDivider;break;}}
|
||||
if(nextDivider&&!nextDivider.detail.collapsed){this.layout.offsetY+=nextDivider.headerHeight;}}
|
||||
if(nextDivider&&nextDivider.detail.collapsed){item.setPosition(idx);continue;}}
|
||||
if(x>0&&item.gridWidth>1&&x+item.gridWidth>=this.layout.cols){var remaining=this.layout.cols-x;this.createPlaceholders([x,y],idx,remaining);idx+=remaining;item=this.items[idx];var lastItemInRow=this.items[idx-1];step(lastItemInRow);}
|
||||
item.setPosition(idx);if(!options.skipItems){item.hasCachedIcon&&++pendingCachedIcons;var xPosition=x*this.layout.gridItemWidth;if(isRTL){xPosition=(this.layout.constraintSize-this.layout.gridItemWidth)-
|
||||
xPosition;}
|
||||
item.setCoordinates(xPosition,this.layout.offsetY);if(!item.active){item.render();}
|
||||
if(item.detail.type==='divider'){if(oddDivider){item.element.classList.add('odd');}else{item.element.classList.remove('odd');}}}
|
||||
x+=item.gridWidth;if((x>=this.layout.cols)&&(idx<this.items.length-1)){step(item);}}
|
||||
if(this.layout.offsetY!=oldHeight){if(this.dragdrop&&this.dragdrop.inDragAction){this.layout.offsetY=oldHeight;}else{this.element.dispatchEvent(new CustomEvent('gaiagrid-resize',{detail:this.layout.offsetY}));}}
|
||||
this.element.setAttribute('cols',this.layout.cols);pendingCachedIcons===0&&onCachedIconRendered();this.loadDragDrop();},loadDragDrop:function(){if(!this.dragdrop&&this.config.features.dragdrop){LazyLoader.load('shared/elements/gaia_grid/js/grid_dragdrop.js',()=>{if(this.dragdrop){return;}
|
||||
this.dragdrop=new GridDragDrop(this);});}}};exports.GridView=GridView;}(window));
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const pinchThreshold = Math.round(window.innerWidth / 12);
|
||||
|
||||
function GridZoom(gridView) {
|
||||
this.gridView = gridView;
|
||||
|
||||
this.touches = 0;
|
||||
this.zoomStartTouches = [];
|
||||
this.zoomStartDistance = 0;
|
||||
|
||||
this.container = document.getElementById('zoom');
|
||||
this.arrows = this.container.querySelector('.arrows');
|
||||
this.curtain = this.container.querySelector('.curtain');
|
||||
this.indicator = this.container.querySelector('.indicator');
|
||||
|
||||
this.start();
|
||||
window.addEventListener('gaiagrid-dragdrop-begin', this.stop.bind(this));
|
||||
window.addEventListener('gaiagrid-dragdrop-finish', this.start.bind(this));
|
||||
}
|
||||
|
||||
GridZoom.prototype = {
|
||||
|
||||
/**
|
||||
* Starts listening for touchstart events.
|
||||
* This is what starts listening events to start the drag & drop operation.
|
||||
*/
|
||||
start: function() {
|
||||
window.addEventListener('touchstart', this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops listening for touchstart events.
|
||||
* This will effectively disable pinch-to-zoom.
|
||||
*/
|
||||
stop: function() {
|
||||
window.removeEventListener('touchstart', this);
|
||||
},
|
||||
|
||||
_attachGestureListeners: function() {
|
||||
window.addEventListener('touchmove', this);
|
||||
window.addEventListener('touchend', this);
|
||||
},
|
||||
|
||||
_stopGestureListeners: function() {
|
||||
window.removeEventListener('touchmove', this);
|
||||
window.removeEventListener('touchend', this);
|
||||
},
|
||||
|
||||
_resetState: function() {
|
||||
this.zoomInProgress = false;
|
||||
|
||||
this.container.hidden = true;
|
||||
this.indicator.classList.remove('active');
|
||||
this.arrows.classList.remove('zooming', 'grow', 'shrink');
|
||||
this.arrows.style.transform = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* General Event Handler
|
||||
*/
|
||||
handleEvent: function(e) {
|
||||
if (e.type === 'touchend' && this.zoomStartTouches) {
|
||||
if (!this.zoomInProgress) {
|
||||
this._resetState();
|
||||
}
|
||||
|
||||
this._stopGestureListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.touches || e.touches.length !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort touches by ascending pageX position.
|
||||
var touches = [e.touches[0], e.touches[1]].sort(function(a, b) {
|
||||
return a.pageX - b.pageX;
|
||||
});
|
||||
|
||||
var touchDistance = Math.sqrt(
|
||||
(touches[0].pageX - touches[1].pageX) *
|
||||
(touches[0].pageX - touches[1].pageX) +
|
||||
(touches[0].pageY - touches[1].pageY) *
|
||||
(touches[0].pageY - touches[1].pageY));
|
||||
|
||||
var layout = this.gridView.layout;
|
||||
|
||||
switch(e.type) {
|
||||
case 'touchstart':
|
||||
this.container.hidden = false;
|
||||
this.zoomStartTouches = touches;
|
||||
this.zoomStartDistance = touchDistance;
|
||||
|
||||
if (layout.cols < layout.maxIconsPerRow) {
|
||||
this.arrows.classList.add('grow');
|
||||
} else {
|
||||
this.arrows.classList.add('shrink');
|
||||
}
|
||||
|
||||
this.indicator.dataset.cols = layout.cols;
|
||||
this._attachGestureListeners();
|
||||
break;
|
||||
|
||||
case 'touchmove':
|
||||
if (layout.cols < layout.maxIconsPerRow &&
|
||||
touchDistance < this.zoomStartDistance &&
|
||||
Math.abs(touchDistance - this.zoomStartDistance) >
|
||||
pinchThreshold) {
|
||||
this.gridView.cols = layout.maxIconsPerRow;
|
||||
this.zoomInProgress = true;
|
||||
} else if (layout.cols > layout.minIconsPerRow &&
|
||||
touchDistance > this.zoomStartDistance &&
|
||||
Math.abs(touchDistance - this.zoomStartDistance) >
|
||||
pinchThreshold) {
|
||||
this.gridView.cols = layout.minIconsPerRow;
|
||||
this.zoomInProgress = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// For now just implement a canned animation
|
||||
this._stopGestureListeners();
|
||||
|
||||
var ontransitionend = function() {
|
||||
this.arrows.removeEventListener('transitionend', ontransitionend);
|
||||
// Change the indicator color at the end.
|
||||
this.indicator.classList.add('active');
|
||||
|
||||
this.gridView.render();
|
||||
|
||||
// Reset zoom element state after a set time.
|
||||
var zoomHideTime = 400;
|
||||
setTimeout(this._resetState.bind(this), zoomHideTime);
|
||||
}.bind(this);
|
||||
|
||||
this.arrows.addEventListener('transitionend', ontransitionend);
|
||||
|
||||
this.indicator.dataset.cols = layout.cols;
|
||||
|
||||
this.arrows.classList.add('zooming');
|
||||
// Force a sync reflow
|
||||
document.body.clientHeight;
|
||||
var scaleTransform = 'scale(1)';
|
||||
if (this.zoomInProgress && touchDistance < this.zoomStartDistance) {
|
||||
scaleTransform = 'scale(0.4)';
|
||||
}
|
||||
this.arrows.style.transform = scaleTransform;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.GridZoom = GridZoom;
|
||||
|
||||
}(window));
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
;'use strict';(function(exports){const TYPE='bookmark';function Bookmark(record,features){this.detail=record;this.features=features||{};this.detail.type=TYPE;}
|
||||
Bookmark.prototype={__proto__:GaiaGrid.GridItem.prototype,renderer:GridIconRenderer.TYPE.FAVICON,get pixelHeight(){return this.grid.layout.gridItemHeight;},gridWidth:1,get name(){return this.detail.name;},get icon(){return this.detail.icon||this.defaultIcon;},get identifier(){return this.detail.id;},update:GaiaGrid.GridItem.prototype.updateFromDatastore,isEditable:function(){return this.features&&this.features.isEditable!==false;},isRemovable:function(){return this.features&&this.features.isRemovable!==false;},isDraggable:function(){return this.features&&this.features.isDraggable!==false;},render:function(){GaiaGrid.GridItem.prototype.render.call(this);this.element.classList.add('bookmark');if(this.isEditable()){this.element.classList.add('editable');}},launch:function(){var features={name:this.name,icon:this.icon,remote:true};var url=this.detail.url;if(this.features.search){features.searchName=this.name;features.searchUrl=url;}
|
||||
window.open(url,'_samescope',Object.keys(features).map(function eachFeature(key){return encodeURIComponent(key)+'='+
|
||||
encodeURIComponent(features[key]);}).join(','));},edit:function(){new MozActivity({name:'save-bookmark',data:{type:'url',url:this.detail.id}});}};exports.GaiaGrid.Bookmark=Bookmark;}(window));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;'use strict';(function(exports){function Divider(){this.detail={type:'divider',index:0};}
|
||||
Divider.prototype={__proto__:GaiaGrid.GridItem.prototype,x:0,y:0,headerHeight:0,get pixelHeight(){return(this.grid.layout.cols>3)?50:60;},gridWidth:4,scale:1,render:function(){if(!this.element){var divider=this.element=document.createElement('section');divider.className='divider';var span=document.createElement('span');span.className='spacer';divider.appendChild(span);this.grid.element.appendChild(divider);}
|
||||
this.element.style.transform='translate(0 ,'+this.y+'px)';},remove:function(){if(this.element){this.element.parentNode.removeChild(this.element);}},isDraggable:function(){return false;}};exports.GaiaGrid.Divider=Divider;}(window));
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
;'use strict';(function(exports){const ICON_BLOB_DECORATED_EVENT='iconblobdecorated';const ICON_BLOB_ERROR_EVENT='gaiagrid-iconbloberror';const FETCH_XHR_TIMEOUT=10000;function fetchBlob(uri){return new Promise(function(accept,reject){var xhr=new XMLHttpRequest({mozAnon:true,mozSystem:true});xhr.open('GET',uri,true);xhr.responseType='blob';xhr.timeout=FETCH_XHR_TIMEOUT;xhr.send();xhr.onload=function(){var status=xhr.status;if(status!==0&&status!==200){reject(new Error('Got HTTP status '+status+' trying to load '+uri));return;}
|
||||
accept(xhr.response);};xhr.onerror=xhr.ontimeout=function(){reject(new Error('Error while HTTP GET: ',uri));};});}
|
||||
function GridItem(){this.detail={};}
|
||||
GridItem.prototype={x:0,y:0,pixelHeight:0,gridWidth:1,scale:1,persistToDB:true,lastX:null,lastY:null,lastScale:null,get hasCachedIcon(){return this.detail&&this.detail.decoratedIconBlob;},renderer:GridIconRenderer.TYPE.STANDARD,iconState:'none',get defaultIcon(){return'/shared/elements/gaia_grid/images/default_icon.png';},get grid(){return document.getElementsByTagName('gaia-grid')[0]._grid;},isRemovable:function(){return false;},isEditable:function(){return false;},isDraggable:function(){return true;},isIconFromOrigin:function(){return this.icon.startsWith('http')||this.icon.startsWith('app:');},setPosition:function(position){this.detail.index=position;},setCoordinates:function(x,y){this.x=x;this.y=y;},requestAttention:function(){var rect={x:this.x,y:this.y,width:this.gridWidth*this.grid.layout.gridItemWidth,height:this.pixelHeight};this.grid.element.dispatchEvent(new CustomEvent('gaiagrid-attention',{detail:rect}));},closestIconSizeFromList:function(choices){if(!choices){return 0;}
|
||||
var sizes=Object.keys(choices);if(!sizes.length){return 0;}
|
||||
var maxSize=this.grid.layout.gridMaxIconSize;var filter=(a)=>a>=maxSize?a:0;var bestSize=Math.min(...sizes.map(filter));bestSize=bestSize||Math.max(...sizes);return bestSize||0;},closestIconFromList:function(choices){var icon;if(Array.isArray(choices)){var manifest={'icons':choices};var maxSize=this.grid.layout.gridMaxIconSize;icon=window.IconsHelper.getBestIconFromWebManifest(manifest,maxSize);return icon?(new URL(icon,this.app.manifestURL)).href:null;}
|
||||
var accurateSize=this.closestIconSizeFromList(choices);if(accurateSize===0){return this.defaultIcon;}
|
||||
icon=choices[accurateSize];if(!UrlHelper.hasScheme(icon)){icon=this.app.origin+icon;}
|
||||
return icon;},renderIconFromSrc:function(url){var background=new Image();background.src=url;background.onload=this._decorateIcon.bind(this,background);},renderIconFromBlob:function(blob){var background=new Image();background.src=URL.createObjectURL(blob);background.onload=()=>{this._decorateIcon(background);URL.revokeObjectURL(background.src);};background.onerror=()=>{URL.revokeObjectURL(background.src);if(this.hasCachedIcon){this._stampElementWithIcon('blobcache');this._displayDecoratedIcon(this.detail.decoratedIconBlob,true);return;}
|
||||
this.renderIconFromSrc(this.defaultIcon);this._stampElementWithIcon(this.defaultIcon);};},_decorateIcon:function(img){var strategy=this.detail.renderer||this.renderer;this.rendererInstance=new GridIconRenderer(this);this.rendererInstance[strategy](img).then(this._displayDecoratedIcon.bind(this));},_displayDecoratedIcon:function(blob,isCachedIcon){if(!this.element){return;}
|
||||
var style=this.element.style;if(!style.backgroundSize){style.height=this.grid.layout.gridItemHeight+'px';var backgroundHeight=((this.grid.layout.gridIconSize*(1/this.grid.layout.percent))+
|
||||
GridIconRenderer.prototype.unscaledCanvasPadding);if(this.grid.layout.gridItemHeight>backgroundHeight){style.backgroundSize=backgroundHeight+'px';}else{style.backgroundSize=style.height;}}
|
||||
if(isCachedIcon){var url=URL.createObjectURL(blob);style.backgroundImage='url('+url+')';this.element.dataset.backgroundImage=url;var img=new Image();img.onload=img.onerror=()=>{this.grid.element.dispatchEvent(new CustomEvent('cached-icon-rendered'));};img.src=url;return;}
|
||||
this._compareBlobs(blob,this.detail.decoratedIconBlob).then((equal)=>{if(equal){return;}
|
||||
style.backgroundImage='url('+URL.createObjectURL(blob)+')';this.detail.decoratedIconBlob=blob;this.grid.element.dispatchEvent(new CustomEvent(ICON_BLOB_DECORATED_EVENT,{detail:this}));var bgImg=this.element.dataset.backgroundImage;bgImg&&URL.revokeObjectURL(bgImg);});},_compareBlobs:function(blob1,blob2){return new Promise(function(resolve){if(!blob1||!blob2||blob1.type!==blob2.type||blob1.size!==blob2.size){resolve(false);return;}
|
||||
var startBytes=127;var bytesHash=16;var reader=new FileReader();reader.onloadend=function(){var result1=reader.result;reader=new FileReader();reader.onloadend=reader.onerror=function(){resolve(result1===reader.result);};reader.readAsDataURL(blob2.slice(startBytes,startBytes+bytesHash));};reader.onerror=function(){resolve(false);};reader.readAsDataURL(blob1.slice(startBytes,startBytes+bytesHash));});},fetchIconBlob:function(){return fetchBlob(this.icon);},_stampElementWithIcon:function(uri){if(!this.element||uri.startsWith('data:')){return;}
|
||||
if(uri.length>255){uri=uri.slice(0,255);}
|
||||
this.element.dataset.testIcon=uri;},renderIcon:function(renderCachedIcon){var icon=this.icon;this.iconState='pending';if(!icon){return;}
|
||||
if(renderCachedIcon&&this.hasCachedIcon){this._displayDecoratedIcon(this.detail.decoratedIconBlob,true);var resolveIcon=()=>{this.grid.element.removeEventListener('cached-icons-rendered',resolveIcon);this.doRenderIcon(icon);};this.grid.element.addEventListener('cached-icons-rendered',resolveIcon);}else{this.doRenderIcon(icon);}},doRenderIcon:function(icon){if(!this.isIconFromOrigin()){this._stampElementWithIcon(icon);this.renderIconFromSrc(icon);this.iconState='success';return Promise.resolve();}
|
||||
var eventTarget=this.grid.element;var blobNotFound=()=>{this.iconState='error';if(!this.hasCachedIcon){this.renderIconFromSrc(this.defaultIcon);this._stampElementWithIcon(this.defaultIcon);}else{this._stampElementWithIcon('blobcache');}
|
||||
eventTarget.dispatchEvent(new CustomEvent(ICON_BLOB_ERROR_EVENT,{detail:this}));};return this.fetchIconBlob().then((blob)=>{if(!blob){return blobNotFound();}
|
||||
this.renderIconFromBlob(blob);this.iconState='success';this._stampElementWithIcon(icon);}).catch((err)=>{blobNotFound();});},removeFromGrid:function(){var idx=this.grid.items.indexOf(this);if(idx===-1){console.error('Attempting to remove self before item has been added!');return;}
|
||||
this.grid.items.splice(idx,1);delete this.grid.icons[this.identifier];if(this.element){this.element.parentNode.removeChild(this.element);}
|
||||
this.grid.render({from:idx-1});},remove:function(){this.grid.element.dispatchEvent(new CustomEvent('removeitem',{detail:this}));},render:function(){var scale=this.grid.layout.percent*this.scale;if(!this.element){var tile=document.createElement('div');tile.className='icon';tile.dataset.identifier=this.identifier;tile.dataset.isDraggable=this.isDraggable();tile.setAttribute('role','link');tile.setAttribute('tabindex',0);tile.style.width=(this.grid.layout.constraintSize/3)+'px';var nameContainerEl=document.createElement('p');nameContainerEl.style.marginTop=((this.grid.layout.gridIconSize*(1/this.grid.layout.percent))+
|
||||
(GridIconRenderer.prototype.unscaledCanvasPadding/2))+'px';tile.appendChild(nameContainerEl);var nameEl=document.createElement('span');nameEl.className='title';nameEl.setAttribute('dir','auto');nameContainerEl.appendChild(nameEl);if(this.isRemovable()){var removeEl=document.createElement('span');removeEl.className='remove';tile.appendChild(removeEl);}
|
||||
this.element=tile;this.updateTitle();this.renderIcon(true);this.grid.element.appendChild(tile);}
|
||||
this.transform(this.x,this.y,scale);},transform:function(x,y,scale,element){scale=scale||1;if(!element){if(x===this.lastX&&y===this.lastY&&scale===this.lastScale){return;}
|
||||
element=this.element;this.lastX=x;this.lastY=y;this.lastScale=scale;}
|
||||
element.style.transform='translate('+x+'px,'+y+'px) scale('+scale+')';},setActive:function(active){if(active){this.element.classList.add('active');this.active=true;}else{this.element.classList.remove('active');this.active=false;}},updateTitle:function(){if(!this.element){return;}
|
||||
var nameEl=this.element.querySelector('.title');if(this.asyncName){this.asyncName().then(function(name){nameEl.textContent=name;});}else{nameEl.textContent=this.name;}},updateFromDatastore:function(record){var iconChanged=record.icon!==this.icon;var nameChanged=record.name!==this.name;var type=this.detail.type;var lastIcon=this.icon;record.type=type;this.detail=record;if(nameChanged){this.updateTitle();}
|
||||
if(iconChanged&&record.icon){this.renderIcon();}else if(!record.icon){this.detail.icon=lastIcon;}}};exports.GaiaGrid.GridItem=GridItem;}(window));
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
;'use strict';(function(exports){const COLLAPSE_RATIO=0.33;const COLLAPSED_GROUP_SIZE=6;const COLLAPSED_GROUP_MARGIN_LEFT=23;const COLLAPSED_GROUP_MARGIN_RIGHT=53;const TOGGLE_TIMEOUT=250;function Group(detail){this.detail=detail||{};this.detail.type='divider';this.detail.index=0;this.pendingCollapse=this.detail.collapsed=!!this.detail.collapsed;}
|
||||
Group.prototype={__proto__:GaiaGrid.GridItem.prototype,gridWidth:4,get headerHeight(){return this.detail.collapsed?20:30;},pendingCollapse:false,pendingCollapseTimeout:null,backgroundHeight:0,lastBackgroundHeight:null,separatorHeight:0,collapseRatio:COLLAPSE_RATIO,get pixelHeight(){return this.detail.collapsed?this.backgroundHeight:this.separatorHeight;},get size(){var size=0;for(var i=this.detail.index-1,items=this.grid.items;i>=0&&items[i].detail.type!=='divider';i--){size++;}
|
||||
return size;},_createElement:function(){var group=this.element=document.createElement('section');group.className='divider group newly-created';var span=document.createElement('span');span.className='background';group.appendChild(span);this.backgroundSpanElement=span;span=document.createElement('span');span.className='shadow';group.appendChild(span);this.shadowSpanElement=span;span=document.createElement('span');span.className='header';group.appendChild(span);this.headerSpanElement=span;span=document.createElement('span');span.className='gripper';this.headerSpanElement.appendChild(span);var button=document.createElement('button');button.className='toggle';button.dataset.l10nId='gaia-grid-toggle-expanded';this.headerSpanElement.appendChild(button);this.toggleElement=button;span=document.createElement('span');span.className='separator';group.appendChild(span);this.dividerSpanElement=span;this.dividerSpanElement.appendChild(document.createElement('span'));this.grid.element.appendChild(group);this.separatorHeight=this.dividerSpanElement.clientHeight;this.lastBackgroundHeight=null;},_renderChildren:function(nApps){var isRTL=(document.documentElement.dir==='rtl');if(!this.detail.collapsed){this.element.classList.remove('collapsed');return;}
|
||||
this.element.classList.add('collapsed');var y=this.y;var index=this.detail.index;var width=Math.round((this.grid.layout.constraintSize-
|
||||
COLLAPSED_GROUP_MARGIN_LEFT-COLLAPSED_GROUP_MARGIN_RIGHT)/COLLAPSED_GROUP_SIZE);var x=isRTL?(this.grid.layout.constraintSize-COLLAPSED_GROUP_MARGIN_RIGHT):COLLAPSED_GROUP_MARGIN_LEFT;y+=this.headerHeight;var maxGridItemWidth=this.grid.layout.constraintSize/this.grid.layout.minIconsPerRow;this.collapseRatio=(maxGridItemWidth/this.grid.layout.gridItemWidth)*COLLAPSE_RATIO;for(var i=index-nApps;i<index;i++){var item=this.grid.items[i];if(this.detail.collapsed){item.scale=this.collapseRatio;var itemVisible=(i-(index-nApps))<COLLAPSED_GROUP_SIZE;if(!itemVisible){item.setCoordinates(isRTL?x+width:x-width,y);}else{item.setCoordinates(x,y);x+=isRTL?-width:width;}
|
||||
item.render();item.element.classList.add('collapsed');if(!itemVisible){item.element.classList.add('hidden');}}}},getRealYPosition:function(nApps){if(this.detail.collapsed){return this.y;}
|
||||
return this.grid.items[this.detail.index-nApps].y-
|
||||
this.headerHeight;},render:function(){var createdElements=false;if(!this.element){createdElements=true;this._createElement();}
|
||||
var nApps=this.size;this._renderChildren(nApps);var y=this.getRealYPosition(nApps);if(y!==this.lastY){this.headerSpanElement.style.transform='translate(0px, '+y+'px)';}
|
||||
if(this.toggleElement){var toggleLabel=this.detail.collapsed?'collapsed':'expanded';this.toggleElement.dataset.l10nId='gaia-grid-toggle-'+toggleLabel;}
|
||||
if(this.detail.collapsed){this.backgroundHeight=Math.round(this.collapseRatio*this.grid.layout.gridIconSize*1.5);}else{var height=Math.ceil(nApps/this.grid.layout.cols);this.backgroundHeight=(height||1)*this.grid.layout.gridItemHeight;}
|
||||
this.backgroundHeight+=this.headerHeight;if(y!=this.lastY||this.backgroundHeight!==this.lastBackgroundHeight){this.backgroundSpanElement.style.transform='translate(0px, '+y+'px) scale(1, '+this.backgroundHeight+')';this.shadowSpanElement.style.transform='translateY('+y+'px)';this.shadowSpanElement.style.height=this.backgroundHeight+'px';this.dividerSpanElement.style.transform='translate(0px, '+(y+this.backgroundHeight)+'px)';}
|
||||
this.lastBackgroundHeight=this.backgroundHeight;this.lastY=y;this.backgroundHeight+=this.separatorHeight;if(createdElements){this.element.clientTop;this.element.classList.remove('newly-created');}},forEachItem:function(callback){var nApps=this.size;var index=this.detail.index;for(var i=index-nApps;i<index;i++){callback(this.grid.items[i]);}},setActive:function(active){GaiaGrid.GridItem.prototype.setActive.call(this,active);this.collapse();var callback=active?function(item){item.element.classList.add('active');}:function(item){item.element.classList.remove('active');};this.forEachItem(callback);},_rerenderOnToggle:function(){if(this.pendingCollapseTimeout){clearTimeout(this.pendingCollapseTimeout);this.pendingCollapseTimeout=null;}
|
||||
if(this.toggleElement){this.toggleElement.classList.add('toggling');setTimeout(()=>{this.toggleElement.classList.remove('toggling');},TOGGLE_TIMEOUT);}
|
||||
this.pendingCollapseTimeout=setTimeout(()=>{this.detail.collapsed=this.pendingCollapse;this.pendingCollapseTimeout=null;if(!this.detail.collapsed){this.forEachItem(function(item){item.scale=1;if(item.element){item.element.classList.remove('collapsed');item.element.classList.remove('hidden');}});}
|
||||
if(this.active){this.render();}
|
||||
this.grid.render();var dragging=this.grid.dragdrop&&this.grid.dragdrop.inDragAction;if(dragging){this.grid.dragdrop.updateIconPosition();}else{window.dispatchEvent(new CustomEvent('gaiagrid-saveitems'));this.requestAttention();}},20);},collapse:function(){if(this.detail.collapsed){return;}
|
||||
this.pendingCollapse=true;this._rerenderOnToggle();},expand:function(){if(!this.detail.collapsed){return;}
|
||||
this.pendingCollapse=false;this._rerenderOnToggle();},launch:function(target){if(target!==this.toggleElement){return;}
|
||||
if(this.detail.collapsed){this.expand();}else{this.collapse();}},remove:function(){if(this.element){this.element.parentNode.removeChild(this.element);}},isDraggable:function(){return true;},requestAttention:function(){var rect={x:0,y:this.getRealYPosition(this.size),width:this.gridWidth*this.grid.layout.gridItemWidth,height:this.backgroundHeight};this.grid.element.dispatchEvent(new CustomEvent('gaiagrid-attention',{detail:rect}));}};exports.GaiaGrid.Divider=Group;}(window));
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
'use strict';
|
||||
/* global GaiaGrid */
|
||||
/* global GridIconRenderer */
|
||||
/* global MozActivity */
|
||||
/* jshint nonew: false */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const TYPE = 'marketplace-app';
|
||||
|
||||
/**
|
||||
* Represents a single bookmark icon on the homepage.
|
||||
*/
|
||||
function MarketPlaceApp(record) {
|
||||
this.detail = record;
|
||||
this.detail.type = TYPE;
|
||||
}
|
||||
|
||||
MarketPlaceApp.prototype = {
|
||||
|
||||
__proto__: GaiaGrid.Bookmark.prototype,
|
||||
|
||||
renderer: GridIconRenderer.TYPE.STANDARD,
|
||||
|
||||
/**
|
||||
* Returns the icon image path.
|
||||
*/
|
||||
get icon() {
|
||||
var icon = this._accurateIcon;
|
||||
|
||||
if (!icon) {
|
||||
icon = this._accurateIcon = this.closestIconFromList(
|
||||
this.detail.icons);
|
||||
}
|
||||
|
||||
return icon;
|
||||
},
|
||||
|
||||
launch: function() {
|
||||
new MozActivity({
|
||||
name: 'marketplace-app',
|
||||
data: {
|
||||
slug: this.detail.slug
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
exports.GaiaGrid.MarketPlaceApp = MarketPlaceApp;
|
||||
|
||||
}(window));
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
;'use strict';(function(exports){const IDENTIFIER_SEP='-';var MOZAPP_EVENTS=['downloadsuccess','downloaderror','downloadavailable','downloadapplied','progress'];var APP_LOADING='loading';var APP_ERROR='error';var APP_UNRECOVERABLE='unrecoverable';var APP_PAUSED='paused';var APP_READY='ready';function localizeString(str){var userLang=document.documentElement.lang;if(navigator.mozL10n&&navigator.mozL10n.ctx.qps.indexOf(userLang)!==-1){return navigator.mozL10n.qps[userLang].translate(str);}
|
||||
return str;}
|
||||
function Mozapp(app,entryPoint,details){this.app=app;this.entryPoint=entryPoint;this.detail={type:'app',manifestURL:app.manifestURL,entryPoint:entryPoint,index:0,decoratedIconBlob:details&&details.decoratedIconBlob};MOZAPP_EVENTS.forEach(function(type){this.app['on'+type]=this.handleEvent.bind(this);},this);this.setAppState(this._determineState(app));}
|
||||
Mozapp.HIDDEN_ROLES=['system','input','homescreen','search','addon','langpack'];Mozapp.prototype={__proto__:GaiaGrid.GridItem.prototype,_isHiddenRole:function(){var manifest=this.app.manifest;if(!manifest||!manifest.role){return null;}
|
||||
return Mozapp.HIDDEN_ROLES.indexOf(manifest.role)!==-1;},_determineState:function(app){if(app.installState==='pending'&&!app.downloadAvailable&&!app.readyToApplyDownload){return APP_UNRECOVERABLE;}
|
||||
if(app.downloading){return APP_LOADING;}
|
||||
if(app.downloadError&&app.downloadError.name){if(app.downloadError.name==='DOWNLOAD_CANCELED'){return APP_PAUSED;}
|
||||
return APP_ERROR;}
|
||||
if(app.installState==='pending'){return APP_PAUSED;}
|
||||
return APP_READY;},handleEvent:function(event){switch(event.type){case'progress':case'downloaderror':case'downloadsuccess':this.setAppState(this._determineState(this.app));break;case'downloadapplied':if(this._isHiddenRole()){console.warn('Removing hidden app from the grid',this.name);return this.removeFromGrid();}
|
||||
delete this._accurateIcon;delete this._accurateIconSize;this.setAppState(this._determineState(this.app));this.renderIcon();window.dispatchEvent(new CustomEvent('downloadapplied',{detail:{id:this.app.manifestURL}}));break;}},fetchIconBlob:function(){if(!this._accurateIconSize){this._accurateIconSize=this.closestIconSizeFromList(this.descriptor.icons);}
|
||||
var result=this._accurateIconSize?navigator.mozApps.mgmt.getIcon(this.app,this._accurateIconSize,this.entryPoint||''):GaiaGrid.GridItem.prototype.fetchIconBlob.call(this);return result;},get pixelHeight(){return this.grid.layout.gridItemHeight;},gridWidth:1,get name(){var userLang=document.documentElement.lang;if(navigator.mozL10n&&userLang in navigator.mozL10n.qps){return navigator.mozL10n.qps[userLang].translate(this.descriptor.short_name||this.descriptor.name);}
|
||||
var locales=this.descriptor.locales;var localized=locales&&locales[userLang]&&(locales[userLang].short_name||locales[userLang].name);return localized||this.descriptor.short_name||this.descriptor.name;},asyncName:function(){var userLang=document.documentElement.lang;var ep=this.entryPoint||undefined;if(!this.app.getLocalizedValue){return new Promise((resolve,reject)=>{reject();});}
|
||||
return this.app.getLocalizedValue('short_name',userLang,ep).then(shortName=>localizeString(shortName),this.app.getLocalizedValue.bind(this.app,'name',userLang,ep)).then(name=>localizeString(name),()=>this.name);},get icon(){var icon=this._accurateIcon;if(!icon){icon=this._accurateIcon=this.closestIconFromList(this.descriptor.icons);}
|
||||
return icon;},get descriptor(){var manifest=this.app.manifest||this.app.updateManifest;if(this.entryPoint&&manifest.entry_points&&manifest.entry_points[this.entryPoint]){return manifest.entry_points[this.entryPoint];}
|
||||
return manifest;},identifierSeparator:IDENTIFIER_SEP,get identifier(){var identifier=[this.app.manifestURL];if(this.entryPoint){identifier.push(this.entryPoint);}
|
||||
return identifier.join(IDENTIFIER_SEP);},isRemovable:function(){return this.app.removable;},unrecoverableError:function(){navigator.mozApps.mgmt.uninstall(this.app);},cancel:function(){var dialog=new ConfirmDialogHelper({type:'pause',title:{id:'gaia-grid-stop-download-title',args:{name:this.name}},body:'gaia-grid-stop-download-body',cancel:{title:'gaia-grid-cancel'},confirm:{title:'gaia-grid-stop-download-action',type:'danger',cb:()=>this.app.cancelDownload()}});dialog.show(document.body);},resume:function(){var dialog=new ConfirmDialogHelper({type:'resume',title:'gaia-grid-resume-download-title',body:{id:'gaia-grid-resume-download-body',args:{name:this.name}},cancel:{title:'gaia-grid-cancel'},confirm:{title:'gaia-grid-resume-download-action',cb:()=>{this.setAppState(APP_LOADING);this.app.download();}}});dialog.show(document.body);},setAppState:function(state){this.appState=state;if(this.element){this.element.dataset.appState=state;}},render:function(){var needsStateSet=!this.element;GaiaGrid.GridItem.prototype.render.apply(this,arguments);if(needsStateSet){this.setAppState(this.appState);}},launch:function(){var app=this.app;switch(this._determineState(app)){case APP_UNRECOVERABLE:return this.unrecoverableError();case APP_ERROR:case APP_PAUSED:return this.resume();case APP_LOADING:return this.cancel();}
|
||||
window.performance.mark('appLaunch@'+app.origin);if(this.entryPoint){return app.launch(this.entryPoint);}
|
||||
return app.launch();}};exports.GaiaGrid.Mozapp=Mozapp;}(window));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
;'use strict';(function(exports){function Placeholder(){this.detail={type:'placeholder',index:0};}
|
||||
Placeholder.prototype={__proto__:GaiaGrid.GridItem.prototype,get pixelHeight(){return this.grid.layout.gridItemHeight;},createsGroupOnDrop:false,gridWidth:1,persistToDB:false,get name(){return'';},render:function(){if(!this.element){var tile=document.createElement('div');tile.className='icon placeholder';tile.style.height=this.pixelHeight+'px';this.element=tile;this.grid.element.appendChild(tile);}
|
||||
this.transform(this.x,this.y,this.grid.layout.percent);},remove:function(){if(this.element){this.element.parentNode.removeChild(this.element);}}};exports.GaiaGrid.Placeholder=Placeholder;}(window));
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
;window.GaiaGrid=(function(win){'use strict';var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaGridBaseurl||'/shared/elements/gaia_grid/';proto.createdCallback=function(){var shadow=this.createShadowRoot();this._template=template.content.cloneNode(true);this._styleHack();this._grid=new GridView({element:this,features:{dragdrop:this.getAttribute('dragdrop')!==null,zoom:this.getAttribute('zoom')!==null,disableSections:this.getAttribute('disable-sections')!==null}});shadow.appendChild(this._template);};proto.add=function(){this._grid.add.apply(this._grid,arguments);};proto.clear=function(){this._grid.clear.apply(this._grid,arguments);};proto.render=function(){this._grid.layout.calculateSize();this._grid.render.apply(this._grid,arguments);};proto.start=function(){this._grid.start.apply(this._grid,arguments);};proto.stop=function(){this._grid.stop.apply(this._grid,arguments);};proto.getIcons=function(){return this._grid.icons;};proto.getItems=function(){return this._grid.items;};proto.getIcon=function(identifier){return this._grid.icons[identifier];};proto.removeIconByIdentifier=function(identifier){delete this._grid.icons[identifier];};proto.removeItemByIndex=function(itemIndex){this._grid.items.splice(itemIndex,1);};proto.popDivider=function(){var items=this._grid.items;if(items.length&&items[items.length-1].detail.type==='divider'){return items.pop();}
|
||||
return null;};proto.appendItem=function(item){var items=this._grid.items;var lastIndex=items.length-1;if(items[lastIndex].detail.type==='divider'){this.add(item,lastIndex);}else{this.add(item);}};proto.appendItemToExpandedGroup=function(item){var items=this._grid.items;var lastIndex=items.length-1;if(items[lastIndex].detail.type==='divider'&&items[lastIndex].detail.collapsed){items[lastIndex].expand();}
|
||||
this.appendItem(item);};proto.setEditHeaderElement=function(element){if(this._grid.dragdrop){this._grid.dragdrop.editHeaderElement=element;}};Object.defineProperty(proto,'maxIconSize',{get:function(){return this._grid.layout.gridMaxIconSize;}});proto.getIndexLastIcon=function(){var items=this._grid.items;for(var i=this._grid.items.length-1;i>=0;i--){if(items[i]instanceof GaiaGrid.Mozapp){return i;}}};proto.removeNonVisualElements=function(){var nonVisualElements=[GaiaGrid.Placeholder];function isNonVisual(elem){var retVal=false;for(var i=0,iLen=nonVisualElements.length;i<iLen&&!retVal;i++){retVal=elem instanceof nonVisualElements[i];}
|
||||
return retVal;}
|
||||
var i=0;var iLen=this._grid.items.length;while(i<iLen){this._grid.items[i].detail.index=i;if(isNonVisual(this._grid.items[i])){this._grid.items.splice(i,1);iLen-=1;}else{i+=1;}}};proto.moveTo=function(orig,dst){this._grid.items.splice(dst,0,this._grid.items.splice(orig,1)[0]);};proto._styleHack=function(){var style=this._template.querySelector('style');this.appendChild(style.cloneNode(true));};var stylesheet=baseurl+'style.css';var template=document.createElement('template');template.innerHTML=`<style scoped>
|
||||
@import url(${stylesheet});</style>
|
||||
<content></content>`;return document.registerElement('gaia-grid',{prototype:proto});})(window);
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
.icon[data-app-state="loading"]:after {
|
||||
content: "";
|
||||
background: no-repeat top center url('images/app_install_spinner.png');
|
||||
background-size: contain;
|
||||
height: 8.4rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
/**
|
||||
Keyframe must be defined outside of the web component.
|
||||
See bug 1018269
|
||||
*/
|
||||
|
||||
animation: rotate 2.25s linear infinite;
|
||||
}
|
||||
|
||||
.icon[data-app-state="loading"] {
|
||||
/**
|
||||
We must align this value with the value of [data-app-state]:after height the background size
|
||||
here is overridden at the element level elsewhere which causes the spinner to be aligned too
|
||||
low.
|
||||
*/
|
||||
background-size: 8.4rem !important;
|
||||
background-image: url('images/app_installing.png') !important;
|
||||
}
|
||||
|
||||
.icon[data-app-state="unrecoverable"] {
|
||||
background-image: url('images/app_install_unrecoverable.png') !important;
|
||||
}
|
||||
|
||||
.icon[data-app-state="error"] {
|
||||
background-image: url('images/app_install_failed.png') !important;
|
||||
}
|
||||
|
||||
.icon[data-app-state="paused"] {
|
||||
background-image: url('images/app_install_canceled.png') !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
transform-origin: 0 0;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background: no-repeat top center;
|
||||
z-index: 3;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.icon.launching {
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
/* XXX: Override the .remove element */
|
||||
.edit-mode .collection.hovered .remove {
|
||||
background: url(images/add.png) no-repeat;
|
||||
background-size: 2rem;
|
||||
}
|
||||
|
||||
.icon p {
|
||||
pointer-events: none;
|
||||
height: 3.2rem;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.icon .title {
|
||||
display: inline-block;
|
||||
margin-top: 0.1rem;
|
||||
/* We need this rule in order not to hide the drop shadow */
|
||||
padding: 0 0.2rem 1rem;
|
||||
text-shadow: 0.1rem 0.1rem 0.3rem rgba(0, 0, 0, 0.3),
|
||||
0.2rem 0.4rem 1rem rgba(0, 0, 0, 0.4);
|
||||
font-size: 1.4rem;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: calc(100% - 0.4rem);
|
||||
box-sizing: border-box;
|
||||
vertical-align: top;
|
||||
transition: opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
/* We allow more space for text in the search app by hooking into the text-rows attribute on gaia-grid. */
|
||||
[text-rows="3"] .icon .title {
|
||||
height: 5.8rem;
|
||||
}
|
||||
|
||||
[cols="4"] .icon .title {
|
||||
/* Font size is the same independent on grid configuration */
|
||||
font-size: calc(1.2rem * 1.333);
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.94rem;
|
||||
}
|
||||
|
||||
.edit-mode .icon.collapsed {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon.collapsed .title {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.icon.hidden {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding-top: 2.3rem;
|
||||
border-left: 0.6rem solid transparent;
|
||||
border-right: 0.6rem solid transparent;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[cols="4"] .divider {
|
||||
padding-top: 1.6rem;
|
||||
}
|
||||
|
||||
.divider span.spacer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.5));
|
||||
margin: 0 25%;
|
||||
width: 50%;
|
||||
height: 0.2rem;
|
||||
transition: opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
.dragging .icon.active:not(.hidden) {
|
||||
z-index: 6;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.dragging .divider:not(.group),
|
||||
.icon:not(.active),
|
||||
.dropped {
|
||||
transition: transform var(--transition-duration),
|
||||
opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
.icon.active {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.edit-mode .icon .remove {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0.1rem;
|
||||
left: 0.5rem;
|
||||
width: 2.6rem;
|
||||
height: 2.6rem;
|
||||
background: url(images/remove.png) no-repeat;
|
||||
background-size: 2rem;
|
||||
/*
|
||||
Ensure that remove is on top of the icon and has some extra
|
||||
padding so it is easy to click.
|
||||
*/
|
||||
z-index: 200;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.icon .remove,
|
||||
.icon.collapsed .remove,
|
||||
.edit-mode .icon.active .remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-mode .icon.editable .title {
|
||||
background-color: rgba(132, 132, 132, 0.7);
|
||||
border-radius: 2rem;
|
||||
margin-top: .4rem;
|
||||
padding: 0 1rem;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
Disable events for placeholders, so we don't trigger touch events in edit mode.
|
||||
We only do this in edit-mode, otherwise we might trigger entering edit mode
|
||||
instead of the context menu.
|
||||
*/
|
||||
.edit-mode .placeholder {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#confirm-message {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.dragging .group.active {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.divider.group {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.group > span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
transition: background-color var(--transition-duration),
|
||||
opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
.group:not(.active):not(.newly-created) > span {
|
||||
transition: background-color var(--transition-duration),
|
||||
opacity var(--transition-duration),
|
||||
transform var(--transition-duration);
|
||||
}
|
||||
|
||||
.group.active > span {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.group .header {
|
||||
height: 4rem;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.group .header span,
|
||||
.group .header button {
|
||||
position: absolute;
|
||||
height: 4rem;
|
||||
top: 0;
|
||||
|
||||
background-position: center top;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 2rem;
|
||||
|
||||
transition: transform 0.25s, opacity 0.25s;
|
||||
}
|
||||
|
||||
.group .header .gripper {
|
||||
left: calc(50% - 4.2rem);
|
||||
width: 8.4rem;
|
||||
background-image: url('images/move_gripper.png');
|
||||
opacity: 0;
|
||||
transform: translate(0, -100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.edit-mode .group:not(.collapsed) .header .gripper {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.group .header .toggle {
|
||||
width: 5.34rem;
|
||||
right: 0;
|
||||
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-color: transparent;
|
||||
background-image: url('images/collapse_arrow.png');
|
||||
transform-origin: 50% 1.6rem;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.group .header .toggle.toggling {
|
||||
opacity: 0.5;
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
|
||||
.group.collapsed .header .toggle {
|
||||
transform: translateY(2rem) rotateZ(180deg);
|
||||
}
|
||||
|
||||
.group.active .header .toggle {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.group .background {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
height: 0.1rem;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.group.odd:not(.newly-created) .background,
|
||||
.edit-mode .group:not(.newly-created) .background {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.group.active .background {
|
||||
background-color: rgba(51, 51, 51, 0.9);
|
||||
}
|
||||
|
||||
.group.hovering .background,
|
||||
.group.drop-target .background {
|
||||
background-color: rgba(27, 185, 225, 0.5);
|
||||
}
|
||||
|
||||
.group .shadow {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.edit-mode .group.active .shadow {
|
||||
background-color: transparent;
|
||||
box-shadow: 0 0.9rem 1.2rem rgba(0, 0, 0, 0.63);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.group .separator {
|
||||
margin: 0;
|
||||
height: 0.8rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.group .separator > span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.25s, opacity 0.25s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.group.odd .separator {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.group.odd .separator > span {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.edit-mode .group .separator,
|
||||
.edit-mode .group .separator > span {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* For preventing entering edit mode when long-pressing on a placeholder */
|
||||
.edit-mode .group .background,
|
||||
.edit-mode .group .shadow,
|
||||
|
||||
.group.collapsed .header,
|
||||
.group.collapsed .background,
|
||||
.group.collapsed .shadow {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.edit-mode .group.collapsed .background {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.dragging .group:not(.active) .separator {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dragging .group:not(.active):not(.invalid-drop) .separator > span {
|
||||
background-color: #0095dd;
|
||||
transform: scaleY(0.325);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.dragging .group.hovered:not(.active) .separator > span {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* A hint element for dragging items to the top of the group */
|
||||
.edit-mode::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 0.8rem;
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
background-color: #0095dd;
|
||||
|
||||
position: relative;
|
||||
top: -0.8rem;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.edit-mode.hover-over-top::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Shift container downwards when hovering over the top to reveal the group
|
||||
* creation hint.
|
||||
*/
|
||||
.edit-mode {
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
|
||||
.edit-mode.hover-over-top {
|
||||
transform: translateY(0.8rem);
|
||||
}
|
||||
|
||||
/* RTL View */
|
||||
:-moz-dir(rtl) .group .header .toggle {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
Some css properties are global (keyframes) and cannot be specified
|
||||
to to only apply to our shadow dom so we hack around this by including
|
||||
this file without scoping it.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
Bug 1018269 - we need to specify the keyframe outside
|
||||
of the web component...
|
||||
*/
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(1deg)
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
:-moz-dir(rtl) gaia-grid {
|
||||
direction: ltr;
|
||||
}
|
||||
|
After Width: | Height: | Size: 178 B |
|
After Width: | Height: | Size: 245 B |
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
/* global ComponentUtils */
|
||||
|
||||
window.GaiaMenu = (function(win) {
|
||||
// Extend from HTMLElement prototype
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
// Allow baseurl to be overridden (used for demo page)
|
||||
var baseurl = window.GaiaMenuBaseurl ||
|
||||
'/shared/elements/gaia_menu/';
|
||||
|
||||
proto.createdCallback = function () {
|
||||
var shadow = this.createShadowRoot();
|
||||
|
||||
this._template = template.content.cloneNode(true);
|
||||
|
||||
var cancelButton = this._template.querySelector('.gaia-menu-cancel button');
|
||||
|
||||
cancelButton.addEventListener('click', function () {
|
||||
this.hide();
|
||||
this.dispatchEvent(new CustomEvent('gaiamenu-cancel'));
|
||||
}.bind(this));
|
||||
|
||||
shadow.appendChild(this._template);
|
||||
|
||||
ComponentUtils.style.call(this, baseurl);
|
||||
|
||||
document.l10n.ready.then(this.localize.bind(this));
|
||||
document.addEventListener('DOMRetranslated', this.localize.bind(this));
|
||||
};
|
||||
|
||||
proto.localize = function() {
|
||||
document.l10n.formatValue('gaia-menu-cancel').then(value => {
|
||||
this.shadowRoot.querySelector('button').textContent = value;
|
||||
});
|
||||
};
|
||||
|
||||
proto.show = function() {
|
||||
this.removeAttribute('hidden');
|
||||
};
|
||||
|
||||
proto.hide = function() {
|
||||
this.setAttribute('hidden', 'hidden');
|
||||
};
|
||||
|
||||
var template = document.createElement('template');
|
||||
|
||||
template.innerHTML =
|
||||
`<form role="dialog" data-type="action">
|
||||
<content select="header"></content>
|
||||
<menu>
|
||||
<content select="button"></content>
|
||||
<span class="gaia-menu-cancel">
|
||||
<button>Cancel</button>
|
||||
</span>
|
||||
</menu>
|
||||
</form>`;
|
||||
|
||||
// Register and return the constructor
|
||||
return document.registerElement('gaia-menu', { prototype: proto });
|
||||
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
/* ----------------------------------
|
||||
* gaia_menu
|
||||
* ---------------------------------- */
|
||||
|
||||
/* default alignment follows the UI direction, not the content direction */
|
||||
:-moz-dir(ltr) [dir] { text-align: left; }
|
||||
:-moz-dir(rtl) [dir] { text-align: right; }
|
||||
|
||||
/* Main dialog setup */
|
||||
gaia-menu {
|
||||
background-color: #2d2d2d;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 0 0 7rem;
|
||||
/**
|
||||
* Using "font-size: 0" to avoid the unwanted visual space (about 3px) created
|
||||
* by white spaces and line breaks in the code between inline-block elements.
|
||||
*/
|
||||
font-size: 0;
|
||||
color: #fff;
|
||||
display: block;
|
||||
transition: transform ease-in-out var(--transition-duration),
|
||||
visibility var(--transition-duration);
|
||||
}
|
||||
|
||||
gaia-menu[hidden] {
|
||||
transform: translateY(100%);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
gaia-menu:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 0.1rem;
|
||||
height: 100%;
|
||||
margin-inline-start: -0.1rem;
|
||||
}
|
||||
|
||||
header {
|
||||
-moz-box-sizing: border-box;
|
||||
font-size: 1.6rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
height: 5rem;
|
||||
line-height: 4.9rem;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
font-weight: normal;
|
||||
padding: 0 3rem;
|
||||
border-bottom: 0.1rem solid #616262;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.6rem;
|
||||
line-height: 4.9rem;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Generic set of actions in toolbar */
|
||||
header menu.toolbar {
|
||||
height: 100%;
|
||||
margin: 0 -3rem;
|
||||
margin-inline-start: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
header:-moz-dir(ltr) menu.toolbar { float: right; }
|
||||
header:-moz-dir(rtl) menu.toolbar { float: left; }
|
||||
|
||||
header menu.toolbar a,
|
||||
header menu.toolbar button {
|
||||
-moz-box-sizing: border-box;
|
||||
position: relative;
|
||||
width: auto;
|
||||
min-width: 5rem;
|
||||
height: 4.9rem;
|
||||
margin-bottom: 0;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
line-height: 4.9rem;
|
||||
background: none;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
z-index: 5;
|
||||
}
|
||||
header:-moz-dir(ltr) menu.toolbar a,
|
||||
header:-moz-dir(ltr) menu.toolbar button {
|
||||
float: left;
|
||||
}
|
||||
header:-moz-dir(rtl) menu.toolbar a,
|
||||
header:-moz-dir(rtl) menu.toolbar button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
header a,
|
||||
header button {
|
||||
border: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-weight: 400;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.1em;
|
||||
color: #fff;
|
||||
border-radius: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Pressed state */
|
||||
header a:active .icon:after,
|
||||
header button:active .icon:after,
|
||||
header menu.toolbar a:active,
|
||||
header menu.toolbar button:active {
|
||||
background: #008aaa;
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
header menu.toolbar a[aria-disabled="true"],
|
||||
header menu.toolbar button[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Icon definitions */
|
||||
header .icon {
|
||||
position: relative;
|
||||
float: none;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 4rem;
|
||||
height: 4.9rem;
|
||||
margin: 0 -1rem;
|
||||
background: transparent no-repeat center center / 3rem auto;
|
||||
font-size: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header .icon.icon-close {
|
||||
background-image: url(images/icons/close.png);
|
||||
}
|
||||
|
||||
header .icon.icon-back {
|
||||
background-image: url(images/icons/back.png);
|
||||
}
|
||||
|
||||
/* Navigation links (back, cancel, etc) */
|
||||
header > button,
|
||||
header > a {
|
||||
width: 5rem;
|
||||
height: 4.9rem;
|
||||
background: none;
|
||||
overflow: visible;
|
||||
margin: 0 -1rem;
|
||||
margin-inline-start: -3rem;
|
||||
position: relative;
|
||||
}
|
||||
header:-moz-dir(ltr) > button,
|
||||
header:-moz-dir(ltr) > a {
|
||||
float: left;
|
||||
}
|
||||
header:-moz-dir(rtl) > button,
|
||||
header:-moz-dir(rtl) > a {
|
||||
float: right;
|
||||
}
|
||||
|
||||
header > button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header > button .icon,
|
||||
header > a .icon {
|
||||
position: static;
|
||||
display: block;
|
||||
width: 3rem;
|
||||
height: 4.9rem;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
header > button .icon:after,
|
||||
header > a .icon:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
offset-inline-start: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
width: 3rem;
|
||||
height: 4.9rem;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
section {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 7rem;
|
||||
left: 1.5rem;
|
||||
right: 1.5rem;
|
||||
bottom: 2rem;
|
||||
font-size: 1.6rem;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
/* Specific component code */
|
||||
menu {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: calc(100% - 13.5rem); /*header(5rem) + footer(7rem) + padding(1.5rem)*/
|
||||
height: auto;
|
||||
padding-top: 1.5rem;
|
||||
overflow: auto;
|
||||
width: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 7rem;
|
||||
}
|
||||
|
||||
button {
|
||||
width: calc(100% - 3rem);
|
||||
height: 4rem;
|
||||
-moz-box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-weight: normal;
|
||||
line-height: 4rem;
|
||||
outline: none;
|
||||
font-size: 1.6rem;
|
||||
text-shadow: none;
|
||||
padding: 0 1.2rem;
|
||||
margin: 0 1.5rem 1rem;
|
||||
background: rgba(87, 87, 87, 0.8);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
position: relative;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
button:-moz-dir(ltr) { text-align: left; }
|
||||
button:-moz-dir(rtl) { text-align: right; }
|
||||
|
||||
span.gaia-menu-cancel {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
background: #4d4d4d;
|
||||
height: 7rem;
|
||||
offset-inline-start: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
span.gaia-menu-cancel button {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
offset-inline-start: 0;
|
||||
color: #333;
|
||||
background: #d8d8d8;
|
||||
text-align: center;
|
||||
border: none;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
button:nth-last-child(2) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Press state */
|
||||
button:active,
|
||||
span.gaia-menu-cancel button:active {
|
||||
background: #00aacc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
button[disabled] {
|
||||
background-color: rgba(87, 87, 87, 0.8);
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button[disabled]:last-child {
|
||||
background-color: #e7e7e7;
|
||||
color: #a6a6a6;
|
||||
opacity: 1;
|
||||
}
|
||||